@stll/stdnum 0.0.1 → 1.0.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/LICENSE +1 -5
- package/README.md +28 -4
- package/dist/_checksums/mod112.js +5 -5
- package/dist/_checksums/mod112.js.map +1 -1
- package/dist/_util/generate.d.ts +13 -1
- package/dist/_util/generate.js +23 -1
- package/dist/_util/generate.js.map +1 -1
- package/dist/ad/mod.d.ts +3 -2
- package/dist/ad/nrt.js +3 -2
- package/dist/ad/nrt.js.map +1 -1
- package/dist/ae/eid.d.ts +0 -1
- package/dist/ae/mod.d.ts +3 -2
- package/dist/ai/mod.d.ts +3 -2
- package/dist/al/mod.d.ts +3 -2
- package/dist/am/mod.d.ts +3 -2
- package/dist/ar/cbu.d.ts +0 -1
- package/dist/ar/cbu.js +1 -1
- package/dist/ar/cbu.js.map +1 -1
- package/dist/ar/cuit.d.ts +0 -1
- package/dist/ar/cuit.js +12 -12
- package/dist/ar/cuit.js.map +1 -1
- package/dist/ar/mod.d.ts +3 -2
- package/dist/at/mod.d.ts +3 -2
- package/dist/at/tin.d.ts +0 -1
- package/dist/at/uid.d.ts +0 -1
- package/dist/at/vnr.d.ts +3 -2
- package/dist/at/vnr.js +20 -3
- package/dist/at/vnr.js.map +1 -1
- package/dist/au/abn.d.ts +0 -1
- package/dist/au/acn.d.ts +0 -1
- package/dist/au/mod.d.ts +3 -2
- package/dist/au/tfn.d.ts +0 -1
- package/dist/az/mod.d.ts +3 -2
- package/dist/az/voen.d.ts +0 -1
- package/dist/az/voen.js.map +1 -1
- package/dist/ba/jmbg.d.ts +1 -2
- package/dist/ba/jmbg.js +1 -0
- package/dist/ba/jmbg.js.map +1 -1
- package/dist/ba/mod.d.ts +3 -2
- package/dist/bd/mod.d.ts +3 -2
- package/dist/bd/nid.js +10 -11
- package/dist/bd/nid.js.map +1 -1
- package/dist/be/bis.d.ts +0 -1
- package/dist/be/mod.d.ts +3 -2
- package/dist/be/nn.d.ts +0 -1
- package/dist/be/vat.d.ts +0 -1
- package/dist/bg/egn.d.ts +1 -2
- package/dist/bg/egn.js +10 -3
- package/dist/bg/egn.js.map +1 -1
- package/dist/bg/mod.d.ts +3 -2
- package/dist/bg/pnf.d.ts +0 -1
- package/dist/bg/vat.d.ts +0 -1
- package/dist/bh/cpr.d.ts +3 -2
- package/dist/bh/cpr.js +8 -2
- package/dist/bh/cpr.js.map +1 -1
- package/dist/bh/mod.d.ts +3 -2
- package/dist/bic.js +13 -15
- package/dist/bic.js.map +1 -1
- package/dist/br/cnpj.d.ts +0 -1
- package/dist/br/cpf.d.ts +0 -1
- package/dist/br/mod.d.ts +3 -2
- package/dist/by/mod.d.ts +3 -2
- package/dist/by/unp.d.ts +0 -1
- package/dist/by/unp.js +6 -3
- package/dist/by/unp.js.map +1 -1
- package/dist/bz/mod.d.ts +3 -2
- package/dist/bz/tin.d.ts +3 -2
- package/dist/bz/tin.js +9 -1
- package/dist/bz/tin.js.map +1 -1
- package/dist/ca/bn.d.ts +0 -1
- package/dist/ca/mod.d.ts +3 -2
- package/dist/ca/sin.d.ts +0 -1
- package/dist/ch/mod.d.ts +3 -2
- package/dist/ch/ssn.d.ts +0 -1
- package/dist/ch/uid.d.ts +0 -1
- package/dist/ch/vat.d.ts +0 -1
- package/dist/cl/mod.d.ts +3 -2
- package/dist/cl/rut.d.ts +0 -1
- package/dist/cl/rut.js +1 -1
- package/dist/cl/rut.js.map +1 -1
- package/dist/cn/mod.d.ts +3 -2
- package/dist/cn/ric.d.ts +1 -2
- package/dist/cn/ric.js +1 -0
- package/dist/cn/ric.js.map +1 -1
- package/dist/cn/uscc.d.ts +0 -1
- package/dist/cn/uscc.js +7 -6
- package/dist/cn/uscc.js.map +1 -1
- package/dist/co/mod.d.ts +3 -2
- package/dist/co/nit.d.ts +0 -1
- package/dist/co/nit.js +4 -1
- package/dist/co/nit.js.map +1 -1
- package/dist/cr/cpf.js +4 -3
- package/dist/cr/cpf.js.map +1 -1
- package/dist/cr/mod.d.ts +3 -2
- package/dist/creditcard.d.ts +0 -1
- package/dist/cu/mod.d.ts +3 -2
- package/dist/cu/ni.d.ts +4 -3
- package/dist/cu/ni.js +11 -1
- package/dist/cu/ni.js.map +1 -1
- package/dist/cy/mod.d.ts +3 -2
- package/dist/cy/vat.d.ts +0 -1
- package/dist/cz/dic.d.ts +0 -1
- package/dist/cz/ico.d.ts +0 -1
- package/dist/cz/mod.d.ts +3 -2
- package/dist/cz/rc.d.ts +1 -2
- package/dist/cz/rc.js +33 -8
- package/dist/cz/rc.js.map +1 -1
- package/dist/de/handelsreg.js +9 -9
- package/dist/de/handelsreg.js.map +1 -1
- package/dist/de/idnr.d.ts +0 -1
- package/dist/de/idnr.js.map +1 -1
- package/dist/de/mod.d.ts +3 -2
- package/dist/de/stnr.d.ts +0 -1
- package/dist/de/stnr.js.map +1 -1
- package/dist/de/svnr.d.ts +0 -1
- package/dist/de/svnr.js +2 -2
- package/dist/de/svnr.js.map +1 -1
- package/dist/de/vat.d.ts +0 -1
- package/dist/dk/cpr.d.ts +4 -3
- package/dist/dk/cpr.js +12 -2
- package/dist/dk/cpr.js.map +1 -1
- package/dist/dk/cvr.d.ts +0 -1
- package/dist/dk/mod.d.ts +3 -2
- package/dist/dk/vat.d.ts +0 -1
- package/dist/do/mod.d.ts +3 -2
- package/dist/do/rnc.d.ts +0 -1
- package/dist/do/rnc.js +1 -1
- package/dist/do/rnc.js.map +1 -1
- package/dist/ec/mod.d.ts +3 -2
- package/dist/ec/ruc.d.ts +0 -1
- package/dist/ec/ruc.js +1 -1
- package/dist/ec/ruc.js.map +1 -1
- package/dist/ee/ik.d.ts +1 -2
- package/dist/ee/ik.js +12 -4
- package/dist/ee/ik.js.map +1 -1
- package/dist/ee/mod.d.ts +3 -2
- package/dist/ee/registrikood.d.ts +0 -1
- package/dist/ee/registrikood.js +8 -7
- package/dist/ee/registrikood.js.map +1 -1
- package/dist/ee/vat.d.ts +0 -1
- package/dist/eg/mod.d.ts +3 -2
- package/dist/es/cif.d.ts +0 -1
- package/dist/es/cif.js +2 -2
- package/dist/es/cif.js.map +1 -1
- package/dist/es/dni.d.ts +0 -1
- package/dist/es/mod.d.ts +3 -2
- package/dist/es/nie.d.ts +0 -1
- package/dist/es/nie.js +13 -13
- package/dist/es/nie.js.map +1 -1
- package/dist/es/nss.d.ts +0 -1
- package/dist/es/vat.d.ts +0 -1
- package/dist/eu/mod.d.ts +3 -2
- package/dist/eu/vat.d.ts +3 -2
- package/dist/eu/vat.js +43 -2
- package/dist/eu/vat.js.map +1 -1
- package/dist/fi/hetu.d.ts +4 -3
- package/dist/fi/hetu.js +17 -2
- package/dist/fi/hetu.js.map +1 -1
- package/dist/fi/mod.d.ts +3 -2
- package/dist/fi/vat.d.ts +0 -1
- package/dist/fi/ytunnus.d.ts +0 -1
- package/dist/fr/mod.d.ts +3 -2
- package/dist/fr/nif.d.ts +0 -1
- package/dist/fr/nir.d.ts +1 -2
- package/dist/fr/nir.js +1 -0
- package/dist/fr/nir.js.map +1 -1
- package/dist/fr/siren.d.ts +0 -1
- package/dist/fr/siret.d.ts +0 -1
- package/dist/fr/tva.d.ts +0 -1
- package/dist/gb/mod.d.ts +3 -2
- package/dist/gb/nino.js +7 -7
- package/dist/gb/nino.js.map +1 -1
- package/dist/gb/sedol.d.ts +0 -1
- package/dist/gb/sedol.js +8 -7
- package/dist/gb/sedol.js.map +1 -1
- package/dist/gb/utr.d.ts +0 -1
- package/dist/gb/vat.d.ts +0 -1
- package/dist/ge/mod.d.ts +3 -2
- package/dist/gh/mod.d.ts +3 -2
- package/dist/gh/tin.d.ts +0 -1
- package/dist/gh/tin.js +9 -9
- package/dist/gh/tin.js.map +1 -1
- package/dist/gr/amka.d.ts +0 -1
- package/dist/gr/mod.d.ts +3 -2
- package/dist/gr/vat.d.ts +0 -1
- package/dist/gt/mod.d.ts +3 -2
- package/dist/gt/nit.d.ts +0 -1
- package/dist/gt/nit.js +5 -2
- package/dist/gt/nit.js.map +1 -1
- package/dist/hk/hkid.d.ts +0 -1
- package/dist/hk/hkid.js +1 -0
- package/dist/hk/hkid.js.map +1 -1
- package/dist/hk/mod.d.ts +3 -2
- package/dist/hr/mod.d.ts +3 -2
- package/dist/hr/vat.d.ts +0 -1
- package/dist/hu/mod.d.ts +3 -2
- package/dist/hu/vat.d.ts +0 -1
- package/dist/iban.d.ts +0 -1
- package/dist/id/mod.d.ts +3 -2
- package/dist/id/npwp.d.ts +0 -1
- package/dist/ie/mod.d.ts +3 -2
- package/dist/ie/pps.d.ts +0 -1
- package/dist/ie/vat.d.ts +0 -1
- package/dist/il/idnr.d.ts +0 -1
- package/dist/il/mod.d.ts +3 -2
- package/dist/in/aadhaar.d.ts +0 -1
- package/dist/in/gstin.d.ts +0 -1
- package/dist/in/gstin.js +3 -1
- package/dist/in/gstin.js.map +1 -1
- package/dist/in/mod.d.ts +3 -2
- package/dist/in/pan.js +2 -2
- package/dist/in/pan.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/iq/mod.d.ts +3 -2
- package/dist/ir/mod.d.ts +3 -2
- package/dist/ir/nid.d.ts +0 -1
- package/dist/is/kennitala.d.ts +0 -1
- package/dist/is/mod.d.ts +3 -2
- package/dist/isin.d.ts +0 -1
- package/dist/isin.js +9 -9
- package/dist/isin.js.map +1 -1
- package/dist/it/codicefiscale.d.ts +4 -3
- package/dist/it/codicefiscale.js +26 -2
- package/dist/it/codicefiscale.js.map +1 -1
- package/dist/it/iva.d.ts +0 -1
- package/dist/it/mod.d.ts +3 -2
- package/dist/jp/cn.d.ts +0 -1
- package/dist/jp/mod.d.ts +3 -2
- package/dist/jp/mynumber.d.ts +0 -1
- package/dist/kr/mod.d.ts +3 -2
- package/dist/kr/rrn.d.ts +4 -3
- package/dist/kr/rrn.js +13 -2
- package/dist/kr/rrn.js.map +1 -1
- package/dist/kw/civil.d.ts +6 -5
- package/dist/kw/civil.js +18 -3
- package/dist/kw/civil.js.map +1 -1
- package/dist/kw/mod.d.ts +3 -2
- package/dist/kz/iin.d.ts +1 -2
- package/dist/kz/iin.js +1 -0
- package/dist/kz/iin.js.map +1 -1
- package/dist/kz/mod.d.ts +3 -2
- package/dist/lei.d.ts +0 -1
- package/dist/li/mod.d.ts +3 -2
- package/dist/lk/mod.d.ts +3 -2
- package/dist/lk/nic.d.ts +4 -3
- package/dist/lk/nic.js +14 -2
- package/dist/lk/nic.js.map +1 -1
- package/dist/lt/asmens.d.ts +0 -1
- package/dist/lt/mod.d.ts +3 -2
- package/dist/lt/vat.d.ts +0 -1
- package/dist/lu/mod.d.ts +3 -2
- package/dist/lu/vat.d.ts +0 -1
- package/dist/luhn.d.ts +0 -1
- package/dist/luhn.js +7 -0
- package/dist/luhn.js.map +1 -1
- package/dist/lv/mod.d.ts +3 -2
- package/dist/lv/vat.d.ts +0 -1
- package/dist/lv/vat.js +39 -12
- package/dist/lv/vat.js.map +1 -1
- package/dist/ma/ice.d.ts +0 -1
- package/dist/ma/mod.d.ts +3 -2
- package/dist/mc/mod.d.ts +3 -2
- package/dist/mc/tva.d.ts +0 -1
- package/dist/md/idno.d.ts +0 -1
- package/dist/md/mod.d.ts +3 -2
- package/dist/me/mod.d.ts +3 -2
- package/dist/me/pib.d.ts +0 -1
- package/dist/mk/edb.d.ts +0 -1
- package/dist/mk/mod.d.ts +3 -2
- package/dist/mt/mod.d.ts +3 -2
- package/dist/mt/vat.d.ts +0 -1
- package/dist/mu/mod.d.ts +3 -2
- package/dist/mx/clabe.d.ts +0 -1
- package/dist/mx/curp.d.ts +4 -3
- package/dist/mx/curp.js +25 -3
- package/dist/mx/curp.js.map +1 -1
- package/dist/mx/mod.d.ts +3 -2
- package/dist/mx/rfc.d.ts +3 -2
- package/dist/mx/rfc.js +21 -2
- package/dist/mx/rfc.js.map +1 -1
- package/dist/my/mod.d.ts +3 -2
- package/dist/my/nric.d.ts +4 -3
- package/dist/my/nric.js +16 -2
- package/dist/my/nric.js.map +1 -1
- package/dist/ng/mod.d.ts +3 -2
- package/dist/ni/mod.d.ts +3 -2
- package/dist/ni/ruc.d.ts +0 -1
- package/dist/nl/bsn.d.ts +0 -1
- package/dist/nl/mod.d.ts +3 -2
- package/dist/nl/vat.d.ts +0 -1
- package/dist/nl/vat.js +9 -43
- package/dist/nl/vat.js.map +1 -1
- package/dist/no/fodselsnummer.d.ts +1 -2
- package/dist/no/fodselsnummer.js +1 -0
- package/dist/no/fodselsnummer.js.map +1 -1
- package/dist/no/mod.d.ts +3 -2
- package/dist/no/mva.d.ts +0 -1
- package/dist/no/orgnr.d.ts +0 -1
- package/dist/nz/ird.d.ts +0 -1
- package/dist/nz/ird.js +2 -2
- package/dist/nz/ird.js.map +1 -1
- package/dist/nz/mod.d.ts +3 -2
- package/dist/pa/mod.d.ts +3 -2
- package/dist/pa/ruc.d.ts +0 -1
- package/dist/pa/ruc.js +3 -1
- package/dist/pa/ruc.js.map +1 -1
- package/dist/patterns.d.ts +1 -4
- package/dist/patterns.js +80 -23
- package/dist/patterns.js.map +1 -1
- package/dist/pe/mod.d.ts +3 -2
- package/dist/pe/ruc.d.ts +0 -1
- package/dist/pe/ruc.js +9 -9
- package/dist/pe/ruc.js.map +1 -1
- package/dist/ph/mod.d.ts +3 -2
- package/dist/pk/cnic.js.map +1 -1
- package/dist/pk/mod.d.ts +3 -2
- package/dist/pl/mod.d.ts +3 -2
- package/dist/pl/nip.d.ts +0 -1
- package/dist/pl/pesel.d.ts +1 -2
- package/dist/pl/pesel.js +1 -0
- package/dist/pl/pesel.js.map +1 -1
- package/dist/pl/regon.d.ts +0 -1
- package/dist/pt/cc.d.ts +0 -1
- package/dist/pt/cc.js +6 -4
- package/dist/pt/cc.js.map +1 -1
- package/dist/pt/mod.d.ts +3 -2
- package/dist/pt/vat.d.ts +0 -1
- package/dist/ro/cnp.d.ts +1 -2
- package/dist/ro/cnp.js +1 -0
- package/dist/ro/cnp.js.map +1 -1
- package/dist/ro/mod.d.ts +3 -2
- package/dist/ro/vat.d.ts +0 -1
- package/dist/rs/mod.d.ts +3 -2
- package/dist/rs/pib.d.ts +0 -1
- package/dist/ru/inn.d.ts +0 -1
- package/dist/ru/inn.js +1 -1
- package/dist/ru/inn.js.map +1 -1
- package/dist/ru/mod.d.ts +3 -2
- package/dist/se/mod.d.ts +3 -2
- package/dist/se/orgnr.d.ts +0 -1
- package/dist/se/personnummer.d.ts +1 -2
- package/dist/se/personnummer.js +1 -0
- package/dist/se/personnummer.js.map +1 -1
- package/dist/se/vat.d.ts +0 -1
- package/dist/sg/mod.d.ts +3 -2
- package/dist/sg/uen.d.ts +0 -1
- package/dist/sg/uen.js +46 -55
- package/dist/sg/uen.js.map +1 -1
- package/dist/si/emso.d.ts +1 -2
- package/dist/si/emso.js +17 -4
- package/dist/si/emso.js.map +1 -1
- package/dist/si/mod.d.ts +3 -2
- package/dist/si/vat.d.ts +0 -1
- package/dist/sk/dic.d.ts +0 -1
- package/dist/sk/ico.d.ts +0 -1
- package/dist/sk/mod.d.ts +3 -2
- package/dist/sk/rc.d.ts +4 -5
- package/dist/sk/rc.js +5 -2
- package/dist/sk/rc.js.map +1 -1
- package/dist/th/mod.d.ts +3 -2
- package/dist/th/tin.d.ts +0 -1
- package/dist/tr/mod.d.ts +3 -2
- package/dist/tr/tckimlik.d.ts +0 -1
- package/dist/tr/vkn.d.ts +0 -1
- package/dist/tw/mod.d.ts +3 -2
- package/dist/tw/ubn.d.ts +0 -1
- package/dist/tw/ubn.js +1 -1
- package/dist/tw/ubn.js.map +1 -1
- package/dist/types.d.ts +35 -22
- package/dist/ua/edrpou.d.ts +0 -1
- package/dist/ua/edrpou.js +4 -1
- package/dist/ua/edrpou.js.map +1 -1
- package/dist/ua/mod.d.ts +3 -2
- package/dist/us/ein.js +87 -87
- package/dist/us/ein.js.map +1 -1
- package/dist/us/itin.js +3 -4
- package/dist/us/itin.js.map +1 -1
- package/dist/us/mod.d.ts +3 -2
- package/dist/us/rtn.d.ts +0 -1
- package/dist/us/rtn.js +8 -5
- package/dist/us/rtn.js.map +1 -1
- package/dist/uy/mod.d.ts +3 -2
- package/dist/uy/rut.d.ts +0 -1
- package/dist/uy/rut.js +1 -1
- package/dist/uy/rut.js.map +1 -1
- package/dist/ve/mod.d.ts +3 -2
- package/dist/ve/rif.d.ts +0 -1
- package/dist/ve/rif.js +11 -11
- package/dist/ve/rif.js.map +1 -1
- package/dist/vn/mod.d.ts +3 -2
- package/dist/vn/mst.d.ts +0 -1
- package/dist/za/idnr.d.ts +1 -2
- package/dist/za/idnr.js +1 -0
- package/dist/za/idnr.js.map +1 -1
- package/dist/za/mod.d.ts +3 -2
- package/package.json +18 -15
- package/src/_checksums/mod112.ts +7 -6
- package/src/_util/generate.ts +38 -0
- package/src/ad/nrt.ts +12 -4
- package/src/ar/cbu.ts +2 -2
- package/src/ar/cuit.ts +15 -7
- package/src/at/vnr.ts +26 -4
- package/src/az/voen.ts +2 -0
- package/src/ba/jmbg.ts +2 -1
- package/src/bd/nid.ts +4 -4
- package/src/bg/egn.ts +12 -4
- package/src/bh/cpr.ts +10 -1
- package/src/bic.ts +19 -17
- package/src/by/unp.ts +9 -4
- package/src/bz/tin.ts +12 -1
- package/src/cl/rut.ts +4 -2
- package/src/cn/ric.ts +4 -1
- package/src/cn/uscc.ts +20 -8
- package/src/co/nit.ts +6 -2
- package/src/cr/cpf.ts +4 -3
- package/src/cu/ni.ts +22 -2
- package/src/cz/rc.ts +54 -23
- package/src/de/handelsreg.ts +14 -3
- package/src/de/idnr.ts +3 -0
- package/src/de/stnr.ts +4 -0
- package/src/de/svnr.ts +7 -2
- package/src/dk/cpr.ts +16 -2
- package/src/do/rnc.ts +2 -2
- package/src/ec/ruc.ts +2 -2
- package/src/ee/ik.ts +14 -4
- package/src/ee/registrikood.ts +4 -3
- package/src/es/cif.ts +2 -3
- package/src/es/nie.ts +10 -5
- package/src/eu/vat.ts +46 -1
- package/src/fi/hetu.ts +19 -2
- package/src/fr/nir.ts +2 -1
- package/src/gb/nino.ts +11 -10
- package/src/gb/sedol.ts +14 -12
- package/src/gh/tin.ts +4 -3
- package/src/gt/nit.ts +6 -3
- package/src/hk/hkid.ts +2 -1
- package/src/in/gstin.ts +3 -1
- package/src/in/pan.ts +8 -3
- package/src/index.ts +3 -0
- package/src/isin.ts +10 -4
- package/src/it/codicefiscale.ts +40 -2
- package/src/kr/rrn.ts +20 -2
- package/src/kw/civil.ts +25 -9
- package/src/kz/iin.ts +2 -1
- package/src/lk/nic.ts +24 -2
- package/src/luhn.ts +3 -0
- package/src/lv/vat.ts +46 -20
- package/src/mx/curp.ts +41 -5
- package/src/mx/rfc.ts +35 -4
- package/src/my/nric.ts +26 -2
- package/src/nl/vat.ts +11 -66
- package/src/no/fodselsnummer.ts +2 -1
- package/src/nz/ird.ts +4 -4
- package/src/pa/ruc.ts +32 -8
- package/src/patterns.ts +135 -28
- package/src/pe/ruc.ts +6 -7
- package/src/pk/cnic.ts +2 -0
- package/src/pl/pesel.ts +2 -1
- package/src/pt/cc.ts +6 -4
- package/src/ro/cnp.ts +2 -1
- package/src/ru/inn.ts +2 -2
- package/src/se/personnummer.ts +2 -1
- package/src/sg/uen.ts +37 -20
- package/src/si/emso.ts +30 -5
- package/src/sk/rc.ts +13 -4
- package/src/tw/ubn.ts +2 -2
- package/src/types.ts +37 -6
- package/src/ua/edrpou.ts +3 -2
- package/src/us/ein.ts +88 -88
- package/src/us/itin.ts +7 -8
- package/src/us/rtn.ts +16 -11
- package/src/uy/rut.ts +2 -2
- package/src/ve/rif.ts +11 -6
- package/src/za/idnr.ts +2 -1
package/src/patterns.ts
CHANGED
|
@@ -66,10 +66,9 @@ const inferLengths = (v: Validator): number[] => {
|
|
|
66
66
|
* "25 123 891" → [2, 3, 3].
|
|
67
67
|
*/
|
|
68
68
|
const inferGroups = (v: Validator): number[] | null => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const compact = v.compact(v.examples[0]!);
|
|
69
|
+
const first = v.examples?.[0];
|
|
70
|
+
if (first === undefined) return null;
|
|
71
|
+
const compact = v.compact(first);
|
|
73
72
|
const formatted = v.format(compact);
|
|
74
73
|
if (formatted === compact) return null;
|
|
75
74
|
|
|
@@ -98,35 +97,59 @@ const inferGroups = (v: Validator): number[] | null => {
|
|
|
98
97
|
* (e.g., "CZ" for DIČ, "CHE" for Swiss UID).
|
|
99
98
|
*/
|
|
100
99
|
const inferPrefix = (v: Validator): string | null => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const compact = v.compact(
|
|
100
|
+
const examples = v.examples;
|
|
101
|
+
const first = examples?.[0];
|
|
102
|
+
if (!examples || first === undefined) return null;
|
|
103
|
+
const compact = v.compact(first);
|
|
105
104
|
// Prefix already present in compact form.
|
|
106
105
|
// But only if ALL examples share the same prefix
|
|
107
106
|
// (e.g., "CHE" for Swiss UID). If examples have
|
|
108
107
|
// different prefixes (IBAN: GB/DE/FR), there's
|
|
109
108
|
// no fixed prefix — it's part of the value.
|
|
110
109
|
const compactMatch = compact.match(/^([A-Z]+)\d/);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const allSame =
|
|
114
|
-
v.compact(ex).startsWith(
|
|
110
|
+
const compactPrefix = compactMatch?.[1];
|
|
111
|
+
if (compactPrefix !== undefined && examples.length > 1) {
|
|
112
|
+
const allSame = examples.every((ex) =>
|
|
113
|
+
v.compact(ex).startsWith(compactPrefix),
|
|
115
114
|
);
|
|
116
|
-
if (allSame) return
|
|
117
|
-
} else if (
|
|
118
|
-
|
|
115
|
+
if (allSame) return compactPrefix;
|
|
116
|
+
} else if (
|
|
117
|
+
compactPrefix !== undefined &&
|
|
118
|
+
examples.length === 1
|
|
119
|
+
) {
|
|
120
|
+
return compactPrefix;
|
|
119
121
|
}
|
|
120
122
|
// Prefix added only by format()
|
|
121
123
|
// (e.g., "DE" for de.vat, "CZ" for cz.dic)
|
|
122
124
|
const formatted = v.format(compact);
|
|
123
125
|
const fmtMatch = formatted.match(/^([A-Z]+)[\s\-./]?\d/);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
const fmtPrefix = fmtMatch?.[1];
|
|
127
|
+
if (
|
|
128
|
+
fmtPrefix !== undefined &&
|
|
129
|
+
!compact.startsWith(fmtPrefix)
|
|
130
|
+
) {
|
|
131
|
+
return fmtPrefix;
|
|
126
132
|
}
|
|
127
133
|
return null;
|
|
128
134
|
};
|
|
129
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Character class for a string fragment:
|
|
138
|
+
* `\d` if all digits, `[A-Z]` if all letters,
|
|
139
|
+
* `[A-Z0-9]` if mixed.
|
|
140
|
+
*/
|
|
141
|
+
const charClassFor = (s: string): string => {
|
|
142
|
+
let hasLetter = false;
|
|
143
|
+
let hasDigit = false;
|
|
144
|
+
for (const ch of s) {
|
|
145
|
+
if (/[a-zA-Z]/.test(ch)) hasLetter = true;
|
|
146
|
+
if (/\d/.test(ch)) hasDigit = true;
|
|
147
|
+
}
|
|
148
|
+
if (hasLetter && hasDigit) return "[A-Z0-9]";
|
|
149
|
+
if (hasLetter) return "[A-Z]";
|
|
150
|
+
return "\\d";
|
|
151
|
+
};
|
|
152
|
+
|
|
130
153
|
/**
|
|
131
154
|
* Determine the character class needed for the
|
|
132
155
|
* compact form: `\d` if all digits, `[A-Z0-9]`
|
|
@@ -136,18 +159,81 @@ const inferCharClass = (v: Validator): string => {
|
|
|
136
159
|
if (!v.examples || v.examples.length === 0) {
|
|
137
160
|
return "\\d";
|
|
138
161
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
162
|
+
const combined = v.examples
|
|
163
|
+
.map((ex) => v.compact(ex))
|
|
164
|
+
.join("");
|
|
165
|
+
return charClassFor(combined);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
type PerGroup = { size: number; charClass: string };
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Infer per-group sizes AND character classes from
|
|
172
|
+
* format(). Returns groups with per-position char
|
|
173
|
+
* classes, e.g., "12 010188 M 01 1" →
|
|
174
|
+
* sizes: [2, 6, 1, 2, 1]
|
|
175
|
+
* classes: ["\d", "\d", "[A-Z]", "\d", "\d"]
|
|
176
|
+
*
|
|
177
|
+
* Only returns non-null when per-group classes
|
|
178
|
+
* differ (some groups are letters, some digits).
|
|
179
|
+
* Letter-only groups before the first digit group
|
|
180
|
+
* are treated as prefixes and excluded (handled
|
|
181
|
+
* by inferPrefix separately).
|
|
182
|
+
*/
|
|
183
|
+
const inferPerGroup = (v: Validator): PerGroup[] | null => {
|
|
184
|
+
const first = v.examples?.[0];
|
|
185
|
+
if (first === undefined) return null;
|
|
186
|
+
const compact = v.compact(first);
|
|
187
|
+
const formatted = v.format(compact);
|
|
188
|
+
if (formatted === compact) return null;
|
|
189
|
+
|
|
190
|
+
// Only applies to mixed validators (compact has
|
|
191
|
+
// both letters and digits in separate positions,
|
|
192
|
+
// like German SVNR "12010188M011").
|
|
193
|
+
const compactMixed =
|
|
194
|
+
/[a-zA-Z]/.test(compact) && /\d/.test(compact);
|
|
195
|
+
if (!compactMixed) return null;
|
|
196
|
+
|
|
197
|
+
// Also skip if any single part mixes letters and
|
|
198
|
+
// digits (IBAN, ISIN) — inferGroups already
|
|
199
|
+
// handles those correctly with the global class.
|
|
200
|
+
const parts = formatted.split(/[^a-zA-Z0-9]+/);
|
|
201
|
+
const isAlphanumeric = parts.some(
|
|
202
|
+
(p) => /[a-zA-Z]/.test(p) && /\d/.test(p),
|
|
203
|
+
);
|
|
204
|
+
if (isAlphanumeric) return null;
|
|
205
|
+
|
|
206
|
+
// Keep digit groups and letter-only groups that
|
|
207
|
+
// appear AFTER the first digit group (embedded
|
|
208
|
+
// letters). Skip letter-only groups before the
|
|
209
|
+
// first digit group (prefix like "CHE").
|
|
210
|
+
const filtered: PerGroup[] = [];
|
|
211
|
+
let seenDigitGroup = false;
|
|
212
|
+
for (const p of parts) {
|
|
213
|
+
if (p.length === 0) continue;
|
|
214
|
+
if (/\d/.test(p)) {
|
|
215
|
+
seenDigitGroup = true;
|
|
216
|
+
filtered.push({
|
|
217
|
+
size: p.length,
|
|
218
|
+
charClass: charClassFor(p),
|
|
219
|
+
});
|
|
220
|
+
} else if (seenDigitGroup) {
|
|
221
|
+
filtered.push({
|
|
222
|
+
size: p.length,
|
|
223
|
+
charClass: charClassFor(p),
|
|
224
|
+
});
|
|
146
225
|
}
|
|
147
226
|
}
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
|
|
227
|
+
|
|
228
|
+
if (filtered.length <= 1) return null;
|
|
229
|
+
|
|
230
|
+
const firstClass = filtered[0]?.charClass;
|
|
231
|
+
const allSame = filtered.every(
|
|
232
|
+
(g) => g.charClass === firstClass,
|
|
233
|
+
);
|
|
234
|
+
if (allSame) return null;
|
|
235
|
+
|
|
236
|
+
return filtered;
|
|
151
237
|
};
|
|
152
238
|
|
|
153
239
|
/**
|
|
@@ -159,6 +245,18 @@ const groupsToPattern = (
|
|
|
159
245
|
cc: string,
|
|
160
246
|
): string => groups.map((g) => `${cc}{${g}}`).join(SEP);
|
|
161
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Build a group regex with per-group char classes.
|
|
250
|
+
* Produces tighter patterns when groups have different
|
|
251
|
+
* character types (e.g., digits vs letters).
|
|
252
|
+
*/
|
|
253
|
+
const groupsToPatternPerClass = (
|
|
254
|
+
groups: readonly { size: number; charClass: string }[],
|
|
255
|
+
): string =>
|
|
256
|
+
groups
|
|
257
|
+
.map(({ size, charClass }) => `${charClass}{${size}}`)
|
|
258
|
+
.join(SEP);
|
|
259
|
+
|
|
162
260
|
/**
|
|
163
261
|
* Derive a loose candidate-matching regex from
|
|
164
262
|
* a validator. The regex finds potential matches;
|
|
@@ -175,7 +273,16 @@ export const toRegex = (v: Validator): RegExp => {
|
|
|
175
273
|
|
|
176
274
|
let pattern: string;
|
|
177
275
|
|
|
178
|
-
|
|
276
|
+
// Use per-group char classes when groups mix
|
|
277
|
+
// digits and letters (e.g., German SVNR:
|
|
278
|
+
// "12 010188 M 01 1" → [\d, \d, [A-Z], \d, \d]).
|
|
279
|
+
// This prevents overly broad [A-Z0-9] from
|
|
280
|
+
// matching all-caps prose as identifiers.
|
|
281
|
+
const perGroup = inferPerGroup(v);
|
|
282
|
+
|
|
283
|
+
if (perGroup && lengths.length <= 1) {
|
|
284
|
+
pattern = groupsToPatternPerClass(perGroup);
|
|
285
|
+
} else if (groups && lengths.length <= 1) {
|
|
179
286
|
pattern = groupsToPattern(groups, cc);
|
|
180
287
|
} else if (lengths.length === 1) {
|
|
181
288
|
pattern = `${cc}{${lengths[0]}}`;
|
package/src/pe/ruc.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { clean } from "#util/clean";
|
|
18
|
-
import { randomDigits } from "#util/generate";
|
|
18
|
+
import { randomDigits, randomPick } from "#util/generate";
|
|
19
19
|
import { err } from "#util/result";
|
|
20
20
|
import { isdigits } from "#util/strings";
|
|
21
21
|
|
|
@@ -29,7 +29,8 @@ import type { ValidateResult, Validator } from "../types";
|
|
|
29
29
|
*/
|
|
30
30
|
const VALID_PREFIXES = new Set(["10", "15", "17", "20"]);
|
|
31
31
|
|
|
32
|
-
const WEIGHTS = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
|
|
32
|
+
const WEIGHTS = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2] as const;
|
|
33
|
+
const GENERATE_PREFIXES = ["10", "15", "17", "20"] as const;
|
|
33
34
|
|
|
34
35
|
const compact = (value: string): string =>
|
|
35
36
|
clean(value, " -.").trim();
|
|
@@ -42,8 +43,8 @@ const compact = (value: string): string =>
|
|
|
42
43
|
*/
|
|
43
44
|
const calcCheckDigit = (body: string): number => {
|
|
44
45
|
let sum = 0;
|
|
45
|
-
for (
|
|
46
|
-
sum += Number(body[i]) *
|
|
46
|
+
for (const [i, weight] of WEIGHTS.entries()) {
|
|
47
|
+
sum += Number(body[i]) * weight;
|
|
47
48
|
}
|
|
48
49
|
const remainder = 11 - (sum % 11);
|
|
49
50
|
if (remainder >= 10) return remainder - 10;
|
|
@@ -87,9 +88,7 @@ const format = (value: string): string => compact(value);
|
|
|
87
88
|
|
|
88
89
|
/** Generate a random valid Peruvian RUC. */
|
|
89
90
|
const generate = (): string => {
|
|
90
|
-
const
|
|
91
|
-
const prefix =
|
|
92
|
-
prefixes[Math.floor(Math.random() * prefixes.length)]!;
|
|
91
|
+
const prefix = randomPick(GENERATE_PREFIXES);
|
|
93
92
|
const body = prefix + randomDigits(8);
|
|
94
93
|
return body + String(calcCheckDigit(body));
|
|
95
94
|
};
|
package/src/pk/cnic.ts
CHANGED
|
@@ -53,6 +53,8 @@ const validate = (value: string): ValidateResult => {
|
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
// First digit must be a valid province code
|
|
56
|
+
// SAFETY: v.length === 13 above guarantees v[0] exists.
|
|
57
|
+
// eslint-disable-next-line no-non-null-assertion
|
|
56
58
|
if (!VALID_PROVINCES.has(v[0]!)) {
|
|
57
59
|
return err(
|
|
58
60
|
"INVALID_COMPONENT",
|
package/src/pl/pesel.ts
CHANGED
|
@@ -131,7 +131,7 @@ const generate = (): string => {
|
|
|
131
131
|
};
|
|
132
132
|
|
|
133
133
|
/** Polish National Identification Number. */
|
|
134
|
-
const pesel: Validator = {
|
|
134
|
+
const pesel: Validator<ParsedPersonId> = {
|
|
135
135
|
name: "Polish National ID",
|
|
136
136
|
localName:
|
|
137
137
|
"Powszechny Elektroniczny System Ewidencji Ludności",
|
|
@@ -145,6 +145,7 @@ const pesel: Validator = {
|
|
|
145
145
|
examples: ["02070803628"] as const,
|
|
146
146
|
compact,
|
|
147
147
|
format,
|
|
148
|
+
parse,
|
|
148
149
|
validate,
|
|
149
150
|
generate,
|
|
150
151
|
};
|
package/src/pt/cc.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { clean } from "#util/clean";
|
|
12
|
-
import {
|
|
12
|
+
import { randomChar, randomDigits } from "#util/generate";
|
|
13
13
|
import { err } from "#util/result";
|
|
14
14
|
|
|
15
15
|
import type { ValidateResult, Validator } from "../types";
|
|
@@ -24,8 +24,10 @@ const compact = (value: string): string =>
|
|
|
24
24
|
const calcCheckDigit = (value: string): string => {
|
|
25
25
|
let sum = 0;
|
|
26
26
|
for (let i = value.length - 1; i >= 0; i--) {
|
|
27
|
+
const ch = value[i];
|
|
28
|
+
if (ch === undefined) continue;
|
|
27
29
|
const pos = value.length - 1 - i;
|
|
28
|
-
let n = ALPHABET.indexOf(
|
|
30
|
+
let n = ALPHABET.indexOf(ch);
|
|
29
31
|
if (pos % 2 === 0) {
|
|
30
32
|
n *= 2;
|
|
31
33
|
if (n > 9) n = Math.floor(n / 10) + (n % 10);
|
|
@@ -70,8 +72,8 @@ const format = (value: string): string => {
|
|
|
70
72
|
/** Generate a random valid Portuguese CC number. */
|
|
71
73
|
const generate = (): string => {
|
|
72
74
|
const digits = randomDigits(9);
|
|
73
|
-
const v0 = ALPHABET
|
|
74
|
-
const v1 = ALPHABET
|
|
75
|
+
const v0 = randomChar(ALPHABET);
|
|
76
|
+
const v1 = randomChar(ALPHABET);
|
|
75
77
|
const body = digits + v0 + v1;
|
|
76
78
|
const check = calcCheckDigit(body);
|
|
77
79
|
return body + check;
|
package/src/ro/cnp.ts
CHANGED
|
@@ -150,7 +150,7 @@ const generate = (): string => {
|
|
|
150
150
|
};
|
|
151
151
|
|
|
152
152
|
/** Romanian Personal Identification Number. */
|
|
153
|
-
const cnp: Validator = {
|
|
153
|
+
const cnp: Validator<ParsedPersonId> = {
|
|
154
154
|
name: "Romanian Personal ID",
|
|
155
155
|
localName: "Cod Numeric Personal",
|
|
156
156
|
abbreviation: "CNP",
|
|
@@ -163,6 +163,7 @@ const cnp: Validator = {
|
|
|
163
163
|
examples: ["1630615123457"] as const,
|
|
164
164
|
compact,
|
|
165
165
|
format,
|
|
166
|
+
parse,
|
|
166
167
|
validate,
|
|
167
168
|
generate,
|
|
168
169
|
};
|
package/src/ru/inn.ts
CHANGED
|
@@ -28,8 +28,8 @@ const checkDigit = (
|
|
|
28
28
|
weights: readonly number[],
|
|
29
29
|
): number => {
|
|
30
30
|
let sum = 0;
|
|
31
|
-
for (
|
|
32
|
-
sum += Number(value[i]) *
|
|
31
|
+
for (const [i, weight] of weights.entries()) {
|
|
32
|
+
sum += Number(value[i]) * weight;
|
|
33
33
|
}
|
|
34
34
|
return (sum % 11) % 10;
|
|
35
35
|
};
|
package/src/se/personnummer.ts
CHANGED
|
@@ -154,7 +154,7 @@ const generate = (): string => {
|
|
|
154
154
|
};
|
|
155
155
|
|
|
156
156
|
/** Swedish Personal Identity Number. */
|
|
157
|
-
const personnummer: Validator = {
|
|
157
|
+
const personnummer: Validator<ParsedPersonId> = {
|
|
158
158
|
name: "Swedish Personal ID",
|
|
159
159
|
localName: "Personnummer",
|
|
160
160
|
abbreviation: "PN",
|
|
@@ -172,6 +172,7 @@ const personnummer: Validator = {
|
|
|
172
172
|
examples: ["880320-0016"] as const,
|
|
173
173
|
compact,
|
|
174
174
|
format,
|
|
175
|
+
parse,
|
|
175
176
|
validate,
|
|
176
177
|
generate,
|
|
177
178
|
};
|
package/src/sg/uen.ts
CHANGED
|
@@ -72,6 +72,9 @@ const compact = (value: string): string =>
|
|
|
72
72
|
* Validate Business (ROB) UEN (9 chars):
|
|
73
73
|
* 8 digits + 1 check letter.
|
|
74
74
|
*/
|
|
75
|
+
const BUSINESS_WEIGHTS = [10, 4, 9, 3, 8, 2, 7, 1] as const;
|
|
76
|
+
const BUSINESS_CHECK_ALPHA = "XMKECAWLJDB";
|
|
77
|
+
|
|
75
78
|
const validateBusiness = (v: string): ValidateResult => {
|
|
76
79
|
if (!isdigits(v.slice(0, 8))) {
|
|
77
80
|
return err(
|
|
@@ -79,6 +82,9 @@ const validateBusiness = (v: string): ValidateResult => {
|
|
|
79
82
|
"Business UEN must start with 8 digits",
|
|
80
83
|
);
|
|
81
84
|
}
|
|
85
|
+
// SAFETY: caller routes to this only when
|
|
86
|
+
// v.length === 9.
|
|
87
|
+
// eslint-disable-next-line no-non-null-assertion
|
|
82
88
|
if (!/^[A-Z]$/.test(v[8]!)) {
|
|
83
89
|
return err(
|
|
84
90
|
"INVALID_FORMAT",
|
|
@@ -86,13 +92,11 @@ const validateBusiness = (v: string): ValidateResult => {
|
|
|
86
92
|
);
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
const weights = [10, 4, 9, 3, 8, 2, 7, 1];
|
|
90
|
-
const checkAlpha = "XMKECAWLJDB";
|
|
91
95
|
let sum = 0;
|
|
92
|
-
for (
|
|
93
|
-
sum += Number(v[i]) *
|
|
96
|
+
for (const [i, weight] of BUSINESS_WEIGHTS.entries()) {
|
|
97
|
+
sum += Number(v[i]) * weight;
|
|
94
98
|
}
|
|
95
|
-
const expected =
|
|
99
|
+
const expected = BUSINESS_CHECK_ALPHA[sum % 11];
|
|
96
100
|
if (v[8] !== expected) {
|
|
97
101
|
return err(
|
|
98
102
|
"INVALID_CHECKSUM",
|
|
@@ -106,6 +110,9 @@ const validateBusiness = (v: string): ValidateResult => {
|
|
|
106
110
|
* Validate Local Company (ROC) UEN (10 chars):
|
|
107
111
|
* 9 digits (year prefix) + 1 check letter.
|
|
108
112
|
*/
|
|
113
|
+
const LOCAL_WEIGHTS = [10, 8, 6, 4, 9, 7, 5, 3, 1] as const;
|
|
114
|
+
const LOCAL_CHECK_ALPHA = "ZKCMDNERGWH";
|
|
115
|
+
|
|
109
116
|
const validateLocalCompany = (
|
|
110
117
|
v: string,
|
|
111
118
|
): ValidateResult => {
|
|
@@ -115,6 +122,9 @@ const validateLocalCompany = (
|
|
|
115
122
|
"Company UEN must have 9 digits",
|
|
116
123
|
);
|
|
117
124
|
}
|
|
125
|
+
// SAFETY: caller routes to this only when
|
|
126
|
+
// v.length === 10.
|
|
127
|
+
// eslint-disable-next-line no-non-null-assertion
|
|
118
128
|
if (!/^[A-Z]$/.test(v[9]!)) {
|
|
119
129
|
return err(
|
|
120
130
|
"INVALID_FORMAT",
|
|
@@ -122,13 +132,11 @@ const validateLocalCompany = (
|
|
|
122
132
|
);
|
|
123
133
|
}
|
|
124
134
|
|
|
125
|
-
const weights = [10, 8, 6, 4, 9, 7, 5, 3, 1];
|
|
126
|
-
const checkAlpha = "ZKCMDNERGWH";
|
|
127
135
|
let sum = 0;
|
|
128
|
-
for (
|
|
129
|
-
sum += Number(v[i]) *
|
|
136
|
+
for (const [i, weight] of LOCAL_WEIGHTS.entries()) {
|
|
137
|
+
sum += Number(v[i]) * weight;
|
|
130
138
|
}
|
|
131
|
-
const expected =
|
|
139
|
+
const expected = LOCAL_CHECK_ALPHA[sum % 11];
|
|
132
140
|
if (v[9] !== expected) {
|
|
133
141
|
return err(
|
|
134
142
|
"INVALID_CHECKSUM",
|
|
@@ -143,7 +151,12 @@ const validateLocalCompany = (
|
|
|
143
151
|
* R/S/T + 2 digits + 2-letter type + 4 digits
|
|
144
152
|
* + 1 check letter.
|
|
145
153
|
*/
|
|
154
|
+
const OTHER_WEIGHTS = [4, 3, 5, 3, 10, 2, 2, 5, 7] as const;
|
|
155
|
+
|
|
146
156
|
const validateOther = (v: string): ValidateResult => {
|
|
157
|
+
// SAFETY: caller routes to this only when
|
|
158
|
+
// v.length === 10.
|
|
159
|
+
// eslint-disable-next-line no-non-null-assertion
|
|
147
160
|
const prefix = v[0]!;
|
|
148
161
|
if (prefix !== "R" && prefix !== "S" && prefix !== "T") {
|
|
149
162
|
return err(
|
|
@@ -171,17 +184,18 @@ const validateOther = (v: string): ValidateResult => {
|
|
|
171
184
|
);
|
|
172
185
|
}
|
|
173
186
|
|
|
174
|
-
const weights = [4, 3, 5, 3, 10, 2, 2, 5, 7];
|
|
175
187
|
let sum = 0;
|
|
176
|
-
for (
|
|
177
|
-
const
|
|
188
|
+
for (const [i, weight] of OTHER_WEIGHTS.entries()) {
|
|
189
|
+
const ch = v[i];
|
|
190
|
+
if (ch === undefined) continue;
|
|
191
|
+
const idx = OTHER_ALPHA.indexOf(ch);
|
|
178
192
|
if (idx === -1) {
|
|
179
193
|
return err(
|
|
180
194
|
"INVALID_FORMAT",
|
|
181
|
-
`Unexpected character at position ${i + 1}: ${
|
|
195
|
+
`Unexpected character at position ${i + 1}: ${ch}`,
|
|
182
196
|
);
|
|
183
197
|
}
|
|
184
|
-
sum += idx *
|
|
198
|
+
sum += idx * weight;
|
|
185
199
|
}
|
|
186
200
|
const expected =
|
|
187
201
|
OTHER_ALPHA[(((sum - 5) % 11) + 11) % 11];
|
|
@@ -218,6 +232,8 @@ const validate = (value: string): ValidateResult => {
|
|
|
218
232
|
|
|
219
233
|
// 10 chars: first char digit => local company,
|
|
220
234
|
// otherwise "other" entity
|
|
235
|
+
// SAFETY: v.length === 10 here.
|
|
236
|
+
// eslint-disable-next-line no-non-null-assertion
|
|
221
237
|
if (isdigits(v[0]!)) {
|
|
222
238
|
return validateLocalCompany(v);
|
|
223
239
|
}
|
|
@@ -228,14 +244,15 @@ const format = (value: string): string => compact(value);
|
|
|
228
244
|
|
|
229
245
|
/** Generate a random valid Singapore UEN (business format). */
|
|
230
246
|
const generate = (): string => {
|
|
231
|
-
const weights = [10, 4, 9, 3, 8, 2, 7, 1];
|
|
232
|
-
const checkAlpha = "XMKECAWLJDB";
|
|
233
247
|
const body = randomDigits(8);
|
|
234
248
|
let sum = 0;
|
|
235
|
-
for (
|
|
236
|
-
sum += Number(body[i]) *
|
|
249
|
+
for (const [i, weight] of BUSINESS_WEIGHTS.entries()) {
|
|
250
|
+
sum += Number(body[i]) * weight;
|
|
237
251
|
}
|
|
238
|
-
|
|
252
|
+
// SAFETY: sum % 11 is in 0..10 and BUSINESS_CHECK_ALPHA
|
|
253
|
+
// has 11 characters.
|
|
254
|
+
// eslint-disable-next-line no-non-null-assertion
|
|
255
|
+
const check = BUSINESS_CHECK_ALPHA[sum % 11]!;
|
|
239
256
|
return body + check;
|
|
240
257
|
};
|
|
241
258
|
|
package/src/si/emso.ts
CHANGED
|
@@ -5,8 +5,14 @@
|
|
|
5
5
|
* encoding DDMMYYY (7-digit birth year), region code,
|
|
6
6
|
* serial number, and a check digit.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Canonical source:
|
|
9
|
+
* - Slovenia government registry overview
|
|
10
|
+
*
|
|
11
|
+
* This is the source behind the stricter register
|
|
12
|
+
* code check (50-59) and the decision to reject
|
|
13
|
+
* future birth dates in the embedded date field.
|
|
14
|
+
*
|
|
15
|
+
* @see https://www.gov.si/teme/registri-in-evidence-prebivalstva/
|
|
10
16
|
*/
|
|
11
17
|
|
|
12
18
|
import { weightedSum } from "#checksums/weighted-sum";
|
|
@@ -75,6 +81,23 @@ const validate = (value: string): ValidateResult => {
|
|
|
75
81
|
"EMŠO contains an invalid date",
|
|
76
82
|
);
|
|
77
83
|
}
|
|
84
|
+
const birthDate = new Date(yyyy, mm - 1, dd);
|
|
85
|
+
const today = new Date();
|
|
86
|
+
today.setHours(0, 0, 0, 0);
|
|
87
|
+
if (birthDate > today) {
|
|
88
|
+
return err(
|
|
89
|
+
"INVALID_COMPONENT",
|
|
90
|
+
"EMŠO cannot contain a future birth date",
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const register = Number(v.slice(7, 9));
|
|
95
|
+
if (register < 50 || register > 59) {
|
|
96
|
+
return err(
|
|
97
|
+
"INVALID_COMPONENT",
|
|
98
|
+
"EMSO register code must be between 50 and 59",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
78
101
|
|
|
79
102
|
if (calcCheckDigit(v) !== Number(v[12])) {
|
|
80
103
|
return err(
|
|
@@ -120,7 +143,7 @@ const generate = (): string => {
|
|
|
120
143
|
const dd = String(randomInt(1, 28)).padStart(2, "0");
|
|
121
144
|
const mm = String(randomInt(1, 12)).padStart(2, "0");
|
|
122
145
|
const yyy = String(randomInt(900, 999));
|
|
123
|
-
const rr = String(randomInt(50,
|
|
146
|
+
const rr = String(randomInt(50, 59));
|
|
124
147
|
const sss = String(randomInt(0, 999)).padStart(3, "0");
|
|
125
148
|
const partial = dd + mm + yyy + rr + sss;
|
|
126
149
|
const c = partial + String(calcCheckDigit(partial));
|
|
@@ -129,7 +152,7 @@ const generate = (): string => {
|
|
|
129
152
|
};
|
|
130
153
|
|
|
131
154
|
/** Slovenian Unique Master Citizen Number. */
|
|
132
|
-
const emso: Validator = {
|
|
155
|
+
const emso: Validator<ParsedPersonId> = {
|
|
133
156
|
name: "Slovenian Personal ID",
|
|
134
157
|
localName: "Enotna matična številka občana",
|
|
135
158
|
abbreviation: "EMŠO",
|
|
@@ -140,10 +163,12 @@ const emso: Validator = {
|
|
|
140
163
|
candidatePattern: "\\d{13}",
|
|
141
164
|
country: "SI",
|
|
142
165
|
entityType: "person",
|
|
143
|
-
sourceUrl:
|
|
166
|
+
sourceUrl:
|
|
167
|
+
"https://www.gov.si/teme/registri-in-evidence-prebivalstva/",
|
|
144
168
|
examples: ["0101006500006"] as const,
|
|
145
169
|
compact,
|
|
146
170
|
format,
|
|
171
|
+
parse,
|
|
147
172
|
validate,
|
|
148
173
|
generate,
|
|
149
174
|
};
|
package/src/sk/rc.ts
CHANGED
|
@@ -8,11 +8,17 @@
|
|
|
8
8
|
* @see https://www.minv.sk/
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
compact,
|
|
13
|
+
format,
|
|
14
|
+
generate,
|
|
15
|
+
parse,
|
|
16
|
+
validate,
|
|
17
|
+
} from "../cz/rc";
|
|
18
|
+
import type { ParsedPersonId, Validator } from "../types";
|
|
13
19
|
|
|
14
20
|
/** Slovak Birth Number. */
|
|
15
|
-
const rc: Validator = {
|
|
21
|
+
const rc: Validator<ParsedPersonId> = {
|
|
16
22
|
name: "Slovak Birth Number",
|
|
17
23
|
localName: "Rodné číslo",
|
|
18
24
|
abbreviation: "RČ",
|
|
@@ -21,11 +27,14 @@ const rc: Validator = {
|
|
|
21
27
|
country: "SK",
|
|
22
28
|
entityType: "person",
|
|
23
29
|
sourceUrl: "https://www.minv.sk/",
|
|
30
|
+
lengths: [9, 10] as const,
|
|
24
31
|
examples: ["7103192745"] as const,
|
|
25
32
|
compact,
|
|
26
33
|
format,
|
|
34
|
+
generate,
|
|
35
|
+
parse,
|
|
27
36
|
validate,
|
|
28
37
|
};
|
|
29
38
|
|
|
30
39
|
export default rc;
|
|
31
|
-
export { compact, format, parse, validate };
|
|
40
|
+
export { compact, format, generate, parse, validate };
|
package/src/tw/ubn.ts
CHANGED
|
@@ -48,8 +48,8 @@ const validate = (value: string): ValidateResult => {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
let sum = 0;
|
|
51
|
-
for (
|
|
52
|
-
sum += digitSum(Number(v[i]) *
|
|
51
|
+
for (const [i, weight] of WEIGHTS.entries()) {
|
|
52
|
+
sum += digitSum(Number(v[i]) * weight);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const checksum = sum % 10;
|
package/src/types.ts
CHANGED
|
@@ -116,16 +116,28 @@ export type ValidateResult =
|
|
|
116
116
|
| { valid: false; error: StdnumError };
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
|
-
* Structured data extracted from
|
|
120
|
-
*
|
|
121
|
-
* birth date and gender.
|
|
119
|
+
* Structured data extracted from an identifier that
|
|
120
|
+
* encodes a birth date.
|
|
122
121
|
*/
|
|
123
|
-
export type
|
|
122
|
+
export type ParsedBirthDate = {
|
|
124
123
|
birthDate: Date;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Structured data extracted from a personal ID number
|
|
128
|
+
* that also encodes gender.
|
|
129
|
+
*/
|
|
130
|
+
export type ParsedPersonId = ParsedBirthDate & {
|
|
125
131
|
gender: "male" | "female";
|
|
126
132
|
};
|
|
127
133
|
|
|
128
|
-
export type
|
|
134
|
+
export type ParsedIdentifier =
|
|
135
|
+
| ParsedBirthDate
|
|
136
|
+
| ParsedPersonId;
|
|
137
|
+
|
|
138
|
+
export type Validator<
|
|
139
|
+
TParsed extends ParsedIdentifier | undefined = undefined,
|
|
140
|
+
> = {
|
|
129
141
|
/** English name. */
|
|
130
142
|
name: string;
|
|
131
143
|
/** Name in the local language. */
|
|
@@ -197,4 +209,23 @@ export type Validator = {
|
|
|
197
209
|
* passed to validate() for confirmation.
|
|
198
210
|
*/
|
|
199
211
|
candidatePattern?: string;
|
|
200
|
-
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Extract structured data (e.g. birth date,
|
|
215
|
+
* gender) from a known-valid identifier.
|
|
216
|
+
* Available on personal IDs that encode such
|
|
217
|
+
* data. Returns null if the input cannot be
|
|
218
|
+
* parsed.
|
|
219
|
+
*
|
|
220
|
+
* On the base `Validator` type this is optional
|
|
221
|
+
* with a widened return; narrow it by typing the
|
|
222
|
+
* validator as `Validator<ParsedPersonId>` etc.
|
|
223
|
+
*/
|
|
224
|
+
parse?: (value: string) => ParsedIdentifier | null;
|
|
225
|
+
} & ([TParsed] extends [undefined]
|
|
226
|
+
? unknown
|
|
227
|
+
: { parse: (value: string) => TParsed | null });
|
|
228
|
+
|
|
229
|
+
export type ParsableValidator<
|
|
230
|
+
TParsed extends ParsedIdentifier = ParsedIdentifier,
|
|
231
|
+
> = Validator<TParsed>;
|