Skip to content

Validation round: fix every audited edge case, docs and convention drift - #615

Merged
hyanmandian merged 80 commits into
mainfrom
claude/final-validation
Sep 29, 2026
Merged

hyanmandian merged 80 commits into
mainfrom
claude/final-validation

Conversation

@hyanmandian

@hyanmandian hyanmandian commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Function-by-function validation of every public export against its official source, the project conventions and the docs (EN and PT), then the fixes. First PR of a stack of three: this one (behaviour and docs), then the bundle size work, then the runtime performance work.

How it was checked

Nine independent audits, one per family (documents, payments, phone and e-mail, CEP/states/municipalities, holidays and business days, classification codes, keys/registries/vehicles, text and Standard Schema, and a cross-cutting pass over the public surface). Each one re-derived the rules from the official texts, fetched every @see link, ran the edge cases (masked input, numbers, hostile values), executed every docs and JSDoc example, and compared the datasets with their live sources (IBGE municipalities/states/regions, Anatel DDDs, Correios ranges, Bacen STR banks, CONFAZ/CONCLA/Receita tables): no check-digit algorithm or dataset was wrong. The findings were edge cases, docs that contradicted the code, citations and convention drift.

Output changes (all wrong to correct, none breaking)

Documents and formatters

  • Every format* built on the shared mask helper returns "" for a value with no digits even with pad: true (it returned the full zero mask): formatCpf("", { pad: true }) is now "". Affects the CPF, CNPJ, PIS, CNH, voter ID, CNS, SUFRAMA, CEI, CNO, CAEPF, CEP, certidão, CEST, CNAE, NCM, NF-e key, processo, boleto and legal nature formatters.

Phone and e-mail

  • The four phone validators reject characters outside digits, whitespace and ()+.-/ (isValidPhone("11 98765-4321x") was true).
  • An explicit +55/0055 is stripped at any length (parsePhone("+55 11 9") was "55119"), and every formatPhone mask drops it (nanp gave "(55) 11987-6543"); e164 keeps at most 11 national digits.
  • isValidServicePhone accepts a country code, as isValidPhone({ accept: ["service"] }) already did.
  • isValidEmail caps the local part at 64 and the address at 254 characters and accepts a punycode final label (user@example.xn--p1ai).
  • generatePhone("landline") no longer draws a first digit of 6 (Res. Anatel 777/2025 art. 21, from March 2027).

CEP and address

  • getAddressInfoByCep treats a provider answer whose CEP or state contradicts the request as not found (BrasilAPI answered a Paraná address for the RS CEP 99999-999), and aborts the providers that lost the race.
  • getCepInfoByAddress rejects a blank, non-string or under-3-character city or street with GetCepInfoByAddressValidationError before any request.
  • generateCep only draws inside the ranges a state owns (00000-000–00999-999 and 78900-000–78999-999 are gone).
  • A provider CEP that lost its leading zero (1310100 for 01310-100) is read as the same CEP.

Payments

  • isValidBoleto rejects letters and stray characters, and accepts the 44-digit cobrança bancária barcode (the arrecadação barcode was already accepted); getBoletoInfo reads it too.
  • getBoletoInfo falls back to today for an invalid or non-Date referenceDate (an invalid Date gave the 1997 base date and a non-Date threw).
  • isValidPixPayload requires the CRC to be the last top-level object and rejects a repeated ID (crafted payloads only).
  • A Pix phone key must carry +55 exactly once (+555511987654321 stays invalid, as in 2.4.0; the stricter +55 handling of the phone fixes had made it valid).
  • getFormatLicensePlate and convertLicensePlateToMercosul follow the stricter plate mask of isValidLicensePlate; the formatters and parsers stay lenient, as every formatter reads only letters and digits.

Keys, registries, vehicles

  • formatNfeKey strips the XML Id prefixes (NF3e leaked its 3) and returns "" for a non-string.
  • isValidLicensePlate and isValidRegistroProfissional reject characters outside the mask ("A@B#C1$2%3^4" was true).
  • isValidVin and isValidNfeKey no longer fold ß/ſ into ASCII letters.
  • generateLicensePlate draws the fifth character of a new Mercosul plate from K to Z (A to J is reserved for converted plates, Res. CONTRAN 969/2022 Anexo II).

Classification codes

  • isValidLegalNature accepts numbers like its siblings, and legal nature codes accept the mask only after the third digit ("-2-0-6-2" was valid).
  • isValidCst accepts the bare 2-digit Tabela B codes for tax: "icms" (the NF-e CST field next to orig); nullish options read as none in isValidCst/isValidClassTrib.

Text, schema, holidays

  • capitalize keeps more prepositions in lower case (ao, aos, à, às, para, pela(s), pelo(s), sob, sobre, até, num, numa, ante, após, perante) and S.A typed without the final dot upper case; checked against every IBGE municipality name in original, upper and lower case with 0 changes.
  • toStandardSchema reports a throwing validator as an issue instead of throwing.
  • Amazonas' 8 December ponto facultativo starts in 1999 (Lei Municipal de Manaus nº 496/1999); it was listed from 1900.

New API (additive)

formatCbo (NNNN-NN), formatCfop (N.NNN), parseNbs and a pad option on formatNbs, so every masked classification code has its formatter and parser.

Conventions and docs

  • Generator templates in scripts/ now emit the committed mask regexes (the weekly dataset refresh would have reverted the mask-run change).
  • No top-level new Map/new Set left (lazy caches), no prose // comments left (Stryker/eslint directives stay), one-line JSDoc on every exported type key, normalizeStateCode in its own _internals folder, buildLegalNature moved to _internals (still re-exported, @deprecated, from the 2.4.0 subpath).
  • Every subpath entry re-exports the types its signature names (PixKeyType, CertidaoType, RegistroProfissionalCouncil, NumberToWordsGender, LegalNature, IsValidCnpjOptions, FormatCnpjOptions, LicensePlateFormat).
  • Docs (EN and PT): every contradiction fixed (NFS-e mask, SUFRAMA and voter ID mask characters, service item 1-01, "single separator" wording, invalid generateCnpj examples, ...), notes added where a caller needs them (alphanumeric CNPJ needs version: 2, numbers lose leading zeros, multi-zone states in getTimezoneByState, the exact window of the fator de vencimento (a slip due up to 3,499 days before referenceDate keeps its date, from 3,500 days on it reads as the next cycle), ...), citations updated (e-Financeira Anexo II, Lei 12.876/2013, Bradesco for the 1997 base date, planalto ccivil_03 links), guides fixed (Hono snippet, state-city race and getMunicipalities, address-form links and aria-busy).
  • A business-day property compared a DST-gap midnight with a 01:00 result in America/Sao_Paulo; it now compares calendar days.

For the maintainer

  • Unreleased on main already (not this PR): the mask convention of fix(validators): read the mask of every identifier the way isValidCpf does #611 makes date-shaped strings valid identifiers (isValidCep("29/09/2026"), isValidCnh("65.843.791/599")), and the Carnaval Monday ponto facultativo moves default addBusinessDays results by one day around Carnaval. Both are intended there; worth a line in the 2.5.0 notes.

  • isValidVin no longer enforces the check digit by default since 38a2194 (already on main): values 2.4.0 rejected are now valid. Documented and sourced, but it is the one default that widens against 2.4.0.

  • NCM 85181020 enters into force on 01/10/2026: regenerate the NCM table on or after that date before releasing 2.5.0.

  • Not done on purpose (would be new API or a v3 change): getProcessoJuridicoInfo, getVoterIdInfo, generateCns/generateCei, ISBN-10, credit card format/brand, a B03-10 opt-out for pre-2019 NF-e keys, rejectZeros for IE.

Gates

The tree-shaking: accepted label covers three intended growths: generateCep (+1.2 KB, the Correios ranges table), getAddressInfoByCep (+1.4 KB, the CEP range cross-check and the abort of the losing providers) and isValidServicePhone (+357 B, the country code and the character check).

An independent differential run over 10.5M calls (npm 2.4.0 vs main vs this stack) found no output change outside the list above.

vp check, 8,368 tests with 100% coverage (also under TZ=America/Sao_Paulo), build, jscpd, knip, check:api (no breaking change against 2.4.0: 731 type assertions hold), commitlint.

A local midnight that falls in a daylight saving gap (1 December 1950 in America/Sao_Paulo) is
read as 01:00, and addBusinessDays keeps the time of day of its input, as date-fns does, so the
properties compared a 01:00 result with a midnight expectation. They now compare the day.
…an linearly

Add the contracted and remaining prepositions (ao, aos, à, às, ante, após, até,
num, numa, para, pela, pelas, pelo, pelos, perante, sob, sobre) to the default
lowerCaseWords, and accept S.A without the final dot as a company designation.
"entre" stays out of the list: the municipality "Desterro de Entre Rios" would
otherwise lose its capital. nextWordIndex no longer slices the token array, so
long input is linear instead of quadratic. JSDoc structure and the IBGE claim
are fixed and the prose comments are moved into the JSDoc.

Output changes (wrong to correct): "casa para todos" "Casa Para Todos" to "Casa
para Todos" (same for the other added prepositions when they link two words);
"empresa s.a" "Empresa S.a" to "Empresa S.A". Running capitalize over every
IBGE municipality name in original, upper and lower case changes no output.
A validator that threw made validate throw too, although the schema is meant
to report failures as issues and the JSDoc says it never throws. The call is
now wrapped and a throw counts as an invalid value. The second parameter is
renamed from config to options, which is invisible to callers.

Output change (wrong to correct): a wrapped function that throws used to
propagate the error out of validate and now returns { issues: [...] }. The
isValid* utilities never throw, so their behaviour is the same.
…path entry

Subpath consumers could not name the type of options.gender, since only the
root entry re-exported it. The Based on note of the JSDoc is rewritten as a
clean sentence under the URL. No output change.
…ctions

The number and currency sections now list the rules the code already implements: where the e goes,
the singular, the de before round millions, zero reais for a negative amount that truncates to
nothing, and the cents cutoff above about 90 trillion. English and Portuguese.
…alidate version

The Hono snippet passed a string schema to the param target, which hands the validator an object, so
every request was rejected. The route now validates the JSON body with an object schema. The
VeeValidate line needs version 5, which the guide now says (English and Portuguese).
…omplete CEP

The English guide linked document-field.md, which docsify resolves from the site root and 404s; it
now points at guides/document-field.md like the Portuguese one. The vanilla snippet left aria-busy
on the CEP field when a pending lookup was followed by an incomplete CEP.
… B, A race

The guide imported the deprecated getCities. The four snippets now use getMunicipalities, with the
IBGE code as the option value and the name as the label, since names repeat across states. The size
figure is 148 KB, the size of the municipality table and its helpers in dist. In React and Vue,
picking A, then B, then A again while the first table was still loading left the select on the
loading message forever; the abort now also clears the asked state. The em dash of the intro is
gone. English and Portuguese.
…and keep tables literal

Amazonas 8 December was the only state entry without a since, so it was listed as an optional
holiday for every year from 1900. The earliest norm located is Lei Municipal de Manaus nº 496/1999,
so it now starts in 1999. ELECTION_DATE_OVERRIDES is a plain literal and the memo an object literal
instead of top-level Map instances. The prose comments moved into JSDoc, the HolidayPeriod keys are
documented, the RS decree of 1995 has its URL and the legacy planalto links are the https ccivil_03
ones.

Output change (wrong to correct): getHolidays({ year, stateCode: "AM" }) no longer lists Nossa
Senhora da Conceição on 8 December for years before 1999, so isHoliday is false and isBusinessDay is
true on that day for AM before 1999, where 2.4.0 said the opposite.
…liday counts

The getHolidays example was headed national holidays but mixed optional and religious entries and
cut the list at random; it now lists the 15 entries of 2024. isHoliday says that years outside 1900
to 2099 are false and that optional and religious entries count, and the AM 8 December is documented
as starting in 1999. English, Portuguese and the JSDoc.
The docs and the getNfseKeyInfo JSDoc said a separator anywhere in an NFS-e key is rejected and that
isValidNfseKey('3550308 2 2 ...') is false. The code accepts the mask characters at the boundaries
of the 8 fields and rejects one inside a field, as the isValidNfseKey JSDoc already said. The docs
(EN and PT) and the JSDoc now match the code and the example is true. No output change.
…tNfeKey

formatNfeKey only dropped the letters of the NFe, CTe, MDFe, BPe and NFCom prefixes, so the digit of
NF3e ended up in front of the key and the last digit was cut. It now strips the prefixes like
parseNfeKey and isValidNfeKey do. It also returned digits for values that are not keys (-1, 1.5,
10n, functions); like parseNfeKey it now reads only strings and non-negative safe integers and
returns an empty string otherwise.

Output changes:
- formatNfeKey('NF3e' + key): '3351 7045 ...' (a leading 3, last digit lost) -> '3517 0458 ...'
- formatNfeKey(-1): '1' -> ''
- formatNfeKey(1.5): '15' -> ''
- formatNfeKey(10n): '10' -> ''
- formatNfeKey(() => 1): '1' -> ''
…istroProfissional too

Both validators stripped everything that is not a letter or a digit before checking the shape, so
'A@B#C1$2%3^4', 'ABC1234' followed by an emoji and '12@3#4$5/SP' were valid. The other validators of
the package (isValidNfeKey, isValidCertidao, isValidProcessoJuridico, isValidIsbn) only accept
whitespace, '.', '-' and '/'. isValidLicensePlate now accepts that mask only between the third
character and the last four, and isValidRegistroProfissional accepts it anywhere but rejects any
other character. getFormatLicensePlate and convertLicensePlateToMercosul call isValidLicensePlate,
so they follow.

Output changes (true -> false):
- isValidLicensePlate('A@B#C1$2%3^4'), ('ABC1234' + emoji), ('A-B-C-1-2-3-4'), ('ABC12-34')
- isValidRegistroProfissional({ value: '12@3#4$5/SP', council: 'OAB' }), ({ value: '123456/SP' +
  emoji, council: 'OAB' })
- getFormatLicensePlate('A-BC1234'): 'LLLNNNN' -> null
- convertLicensePlateToMercosul('A-BC1234'): 'ABC1234'-based plate -> ''
…d isValidNfeKey

toUpperCase folds some non-ASCII letters into ASCII ones, so isValidVin('9BWZZZ377VT004' + 'ſ51')
read the long s as S and a 16 character VIN ending in 'ß' became 17 characters ('SS'). isValidNfeKey
had the same fold: a key with 'ſ' in the CNPJ area was valid although parseNfeKey drops that
character, so validity and parsing disagreed. Both now test the raw value against an ASCII pattern
before upper casing.

Output changes (true -> false):
- isValidVin('9BWZZZ377VT004ſ51'), isValidVin('9BWZZZ377VT0042ß')
- isValidNfeKey('35260712ABſ34501DE35550010000001231102030404')
isValidCertidao names CertidaoType in its options and isValidRegistroProfissional names
RegistroProfissionalCouncil in its params, but their own entries only exported the option types.
Both now re-export the type their signature names, like get-nfse-key-info does with StateCode. No
runtime change.
…uer document

isValidNfeKey applies rule B03-10 of the MOC, which the authorizer enforces only for documents sent
after NT 2019.001, so a key issued earlier with a cNF equal to the document number is turned down.
It also checks only the key's own check digit, not the CPF or CNPJ of the issuer. Both are now
stated in the JSDoc and in the EN and PT docs, with the way to check the issuer through
getNfeKeyInfo. No output change.
The behaviour each comment described is already in the JSDoc of the function or obvious from the
code, and the package keeps only directive comments in src. No behaviour change.
…nse plate JSDoc

The paragraph explaining the Resolução CNJ nº 65/2008 and the Resolução CONTRAN nº 969/2022 sat
after the closing fence of @example, so JSDoc tools rendered it as part of the example. It now sits
in the description above @PARAM in the processo jurídico and license plate modules.
formatProcessoJuridico and formatCertidao document the optional options.pad as [options.pad], and
the long NFS-e, GTIN and processo paragraphs are wrapped at the same width. No behaviour change.
…te from K to Z

Anexo II, item 2, of Resolução CONTRAN nº 969/2022 reserves the range A to J to convert an old
format plate (digits 0 to 9), so a new Mercosul plate never carries it in the fifth position.
generateLicensePlate drew that letter from the whole alphabet, so 10 of 26 generated plates were in
the converted-only range. Every generated plate is still accepted by all the validators.

Output change (random distribution): the fifth character of generateLicensePlate() and
generateLicensePlate('LLLNLNN') is now one of K to Z instead of A to Z.
…ter params

The parameter was called options but typed GenerateProcessoJuridicoParams, and the JSDoc documented
options.year and options.court. It is now params everywhere, with the optional fields in brackets.
The call signature and types are unchanged. No behaviour change.
The JSDoc cited NT 2021.003 as v1.50 of September 2026, but the PDF at the linked URL is v1.30 of
December 2023. The rules the library relies on (I03-10 and I12-10, rejections 611 and 612, and the
8, 12, 13 and 14 digit field description) are in that version, so only the version wording changes.
No behaviour change.
…nd quote the outputs

The isValidVin docs did not say that separators and spaces inside the VIN are rejected (the JSDoc
did), the formatLicensePlate docs left out the progressive mask that makes it usable as an input
mask, and the formatCertidao, formatProcessoJuridico and parseProcessoJuridico examples printed
their results without quotes while the other families quote them. EN and PT docs updated together,
plus a test for the VIN separators. No output change.
isValidPhone, isValidMobilePhone, isValidLandlinePhone and isValidServicePhone
reduced the value to digits before judging it, so any character was silently
dropped. A letter is not a mask character. The validators now return false when
the value holds anything other than digits, whitespace and ()+.-/.

Output changes (wrong true becomes false):
- isValidPhone("11 98765-4321x"): true -> false
- isValidMobilePhone("11 98765-4321x"): true -> false
- isValidLandlinePhone("tel 1130000000"): true -> false
- isValidServicePhone("abc190"): true -> false
normalizePhone only removed the country code when 10 or 11 digits were left, so
a number still being typed after an explicit +55 or 0055 kept the 55 and read it
as the DDD. An explicit country code has no ambiguity with DDD 55, so it is now
always removed. A bare 55 keeps the length rule.

Output changes in parsePhone (and so in every reader of normalizePhone), only
for short input that starts with an explicit country code:
- parsePhone("+55 11 9"): "55119" -> "119"
- parsePhone("+55"): "55" -> ""
- parsePhone("0055 (11) 98888"): "00551198888" -> "1198888"
Full length numbers are unchanged.
formatPhone read the digits of the value before looking for a country code, so
it lost the "+" that makes +55 unambiguous, and the sn and nanp masks never
dropped it at all. Every mask now drops an explicit +55 or 0055 (a bare 55 stays
under sn and nanp, since it may be the DDD), and e164 keeps at most the 11
national digits, as international already did.

Output changes:
- formatPhone("+5511987654321"): "55119-8765" -> "11987-6543" (default sn)
- formatPhone("+5511987654321", { mask: "nanp" }): "(55) 11987-6543" -> "(11) 98765-4321"
- formatPhone("+55", { mask: "e164" }): "+5555" -> ""
- formatPhone("+55 11 9", { mask: "auto" }): "55119" -> "+55 11 9"
- formatPhone("+55 11 9", { mask: "international" }): "+55 55 119" -> "+55 11 9"
- formatPhone("119888877660000", { mask: "e164" }): "+55119888877660000" -> "+5511988887766"
isValidPhone with accept: ["service"] read "+55 0800 123 4567" as a service
number, but isValidServicePhone rejected the same value. isValidServicePhone now
resolves the country code the same way. The structural check moves to an
internal so the validator and the resolver no longer import each other.

Output changes (false becomes true):
- isValidServicePhone("+55 0800 123 4567")
- isValidServicePhone("+55 190")
- isValidServicePhone("0055 4004-1234")
- isValidServicePhone("5508001234567")
isValidEmail capped domain labels at 63 characters but not the local part or the
whole address, and rejected a valid punycode top-level domain because the final
label had to be letters only. The local part is now capped at 64 characters and
the address at 254 (RFC 5321, section 4.5.3.1), and a final label may be a
punycode label (xn--). The docs now list what is rejected.

Output changes:
- isValidEmail("a".repeat(65) + "@a.com"): true -> false
- isValidEmail("a".repeat(300) + "@a.com"): true -> false
- isValidEmail("user@example.xn--p1ai"): false -> true
PHONE_MASKS was a top-level new Set and the municipality area code index a
top-level new Map, which defeats tree shaking. Both are now plain literals, the
index filled on first use. Prose comments move out of the source, and the notes
that sat after @returns, @example and @see in getAreaCodeInfo,
getAreaCodesByState and isValidMobilePhone move into the description, so tooling
no longer renders them inside the wrong tag. No output change.
generatePhone drew a landline first digit from 2 to 6. Resolucao Anatel 777/2025,
art. 21 narrows the STFC range to 2 to 5 from 1 March 2027, and isValidLandlinePhone
already documents that change. Drawing 2 to 5 keeps every generated landline valid
before and after it.

Output change: generatePhone("landline") no longer returns a number whose first
digit after the DDD is 6 (the distribution of a random value, not a wrong result).
The formatCurrency and findCodeIndex caches were top-level new Map calls that
bundlers cannot prove pure. They are created on first use now. Prose // comments
in src are gone (the one that carried a reason moved into a JSDoc), and the
isValidPixKey entry re-exports PixKeyType like getPixKeyInfo does. No output
change.
_internals holds one function per folder and read-state-code exported two.
normalizeStateCode now lives in _internals/normalize-state-code with its own
tests, and readStateCode imports it. The eleven utils that only normalize
import it from the new folder. No behavior change.
getBankByCode and getBankByIspb embed the full bank table (about 38 KB) like
getBanks, and the deprecated getMunicipality and getCities embed the
municipality table, but the rule did not name them. Docs only.
@vercel

vercel Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
brazilian-utils Ignored Ignored Preview Sep 29, 2026 11:06am UTC

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 0cd15cdb-087c-4f9a-b8b9-729b405fc99e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@brazilian-utils/brazilian-utils@615

commit: 0ad6d4e

@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Tree-shaking report

❌ 3 size regressions. 73 grew, 18 shrank, 3 new out of 198 exports.

Base Head Δ
Pre-existing exports, all imported 1845.4 KB 1846.9 KB (gzip 328.1 KB) +1.5 KB (+0.1%)
Full import 1845.4 KB 1847.0 KB (gzip 328.1 KB) +1.6 KB (+0.1%)
Exports 195 198 +3

What changed (94)

Export Base Head Δ gzip
🔴 getAddressInfoByCep 3.9 KB 5.3 KB +1.4 KB (+35.4%) 2.1 KB
🔴 generateCep 130 B 1.3 KB +1.2 KB (+926.9%) 471 B
🆕 formatCbo — 491 B new 350 B
🆕 formatCfop — 489 B new 349 B
🔴 isValidServicePhone 706 B 1.0 KB +357 B (+50.6%) 573 B
🟡 getPixPayloadInfo 4.6 KB 4.8 KB +256 B (+5.5%) 2.2 KB
🟡 isValidPixPayload 4.1 KB 4.3 KB +256 B (+6.1%) 2.0 KB
🟡 getBoletoInfo 2.4 KB 2.7 KB +255 B (+10.4%) 1.4 KB
🟡 generatePixPayload 4.5 KB 4.7 KB +233 B (+5.1%) 2.1 KB
🟡 getPixKeyInfo 2.5 KB 2.7 KB +225 B (+8.8%) 1.3 KB
🟡 isValidPixKey 2.3 KB 2.5 KB +225 B (+9.6%) 1.2 KB
🆕 parseNbs — 198 B new 181 B
🟡 isValidLegalNature 741 B 911 B +170 B (+22.9%) 551 B
🟡 isValidLandlinePhone 717 B 886 B +169 B (+23.6%) 559 B
🟡 isValidMobilePhone 758 B 927 B +169 B (+22.3%) 591 B
🟡 getCepInfoByAddress 2.0 KB 2.2 KB +158 B (+7.7%) 1.1 KB
🟡 isValidBoleto 1.6 KB 1.8 KB +139 B (+8.4%) 970 B
🟡 capitalize 2.4 KB 2.5 KB +130 B (+5.3%) 1.2 KB
🟡 parsePhone 370 B 497 B +127 B (+34.3%) 341 B
🟡 formatNfeKey 601 B 724 B +123 B (+20.5%) 461 B
Show the other 74
Export Base Head Δ gzip
🟡 getLegalNature 5.6 KB 5.7 KB +113 B (+2.0%) 1.9 KB
🟡 isValidEmail 171 B 244 B +73 B (+42.7%) 198 B
🟡 convertLicensePlateToMercosul 573 B 632 B +59 B (+10.3%) 425 B
🟡 getFormatLicensePlate 361 B 420 B +59 B (+16.3%) 301 B
🟡 isValidLicensePlate 313 B 372 B +59 B (+18.8%) 280 B
🟡 isValidPhone 1.8 KB 1.9 KB +58 B (+3.1%) 962 B
🟡 isValidVin 848 B 898 B +50 B (+5.9%) 600 B
🟢 isValidClassTrib 1.8 KB 1.8 KB -44 B (-2.4%) 613 B
🟡 generateLicensePlate 266 B 308 B +42 B (+15.8%) 243 B
🟡 isValidRegistroProfissional 1.3 KB 1.3 KB +41 B (+3.2%) 787 B
🟡 toStandardSchema 284 B 324 B +40 B (+14.1%) 254 B
🟡 formatCurrency 985 B 1023 B +38 B (+3.9%) 613 B
🟡 formatNbs 462 B 496 B +34 B (+7.4%) 353 B
🟡 getAreaCodeByMunicipalityCode 165.0 KB 165.1 KB +32 B (+0.0%) 52.2 KB
🟡 getMunicipalitiesByAreaCode 167.8 KB 167.8 KB +32 B (+0.0%) 52.8 KB
🟡 formatCns 483 B 506 B +23 B (+4.8%) 352 B
🟡 formatSuframa 476 B 499 B +23 B (+4.8%) 354 B
🟡 formatBoleto 649 B 668 B +19 B (+2.9%) 407 B
🟡 formatCaepf 487 B 506 B +19 B (+3.9%) 358 B
🟡 formatCei 484 B 503 B +19 B (+3.9%) 356 B
🟡 formatCep 470 B 489 B +19 B (+4.0%) 348 B
🟡 formatCEP 470 B 489 B +19 B (+4.0%) 348 B
🟡 formatCertidao 513 B 532 B +19 B (+3.7%) 361 B
🟡 formatCest 474 B 493 B +19 B (+4.0%) 350 B
🟡 formatCid10 459 B 478 B +19 B (+4.1%) 339 B
🟡 formatCnae 474 B 493 B +19 B (+4.0%) 352 B
🟡 formatCnh 517 B 536 B +19 B (+3.7%) 374 B
🟡 formatCno 488 B 507 B +19 B (+3.9%) 357 B
🟡 formatCnpj 620 B 639 B +19 B (+3.1%) 430 B
🟡 formatCNPJ 620 B 639 B +19 B (+3.1%) 430 B
🟡 formatCpf 521 B 540 B +19 B (+3.6%) 376 B
🟡 formatCPF 521 B 540 B +19 B (+3.6%) 376 B
🟡 formatLegalNature 470 B 489 B +19 B (+4.0%) 349 B
🟡 formatNcm 475 B 494 B +19 B (+4.0%) 352 B
🟡 formatPis 521 B 540 B +19 B (+3.6%) 377 B
🟡 formatProcessoJuridico 490 B 509 B +19 B (+3.9%) 357 B
🟡 formatVoterId 523 B 542 B +19 B (+3.6%) 377 B
🟢 addBusinessDays 9.5 KB 9.5 KB -17 B (-0.2%) 3.3 KB
🟢 differenceInBusinessDays 9.3 KB 9.3 KB -17 B (-0.2%) 3.3 KB
🟢 getHolidays 8.4 KB 8.4 KB -17 B (-0.2%) 2.9 KB
🟢 isBusinessDay 8.8 KB 8.8 KB -17 B (-0.2%) 3.1 KB
🟢 isHoliday 8.7 KB 8.6 KB -17 B (-0.2%) 3.0 KB
🟢 isValidCst 995 B 978 B -17 B (-1.7%) 548 B
🟢 subBusinessDays 9.6 KB 9.6 KB -17 B (-0.2%) 3.4 KB
🟡 formatPhone 2.6 KB 2.6 KB +12 B (+0.4%) 1.2 KB
🟢 generateCpf 702 B 694 B -8 B (-1.1%) 456 B
🟢 generateCPF 702 B 694 B -8 B (-1.1%) 456 B
🟡 getNfeKeyInfo 2.2 KB 2.2 KB +6 B (+0.3%) 1.2 KB
🟡 isValidNfeKey 1.6 KB 1.6 KB +6 B (+0.4%) 958 B
🟡 getCbo 107.6 KB 107.6 KB +5 B (+0.0%) 24.8 KB
🟡 getCest 111.6 KB 111.6 KB +5 B (+0.0%) 24.1 KB
🟡 getCfop 66.8 KB 66.8 KB +5 B (+0.0%) 5.3 KB
🟡 getCnae 86.6 KB 86.6 KB +5 B (+0.0%) 18.0 KB
🟡 getNbs 75.6 KB 75.6 KB +5 B (+0.0%) 11.6 KB
🟡 getServiceItem 26.1 KB 26.1 KB +5 B (+0.0%) 8.1 KB
🟡 isValidBankAccount 5.1 KB 5.1 KB +5 B (+0.1%) 1.9 KB
🟡 isValidCbo 6.7 KB 6.7 KB +5 B (+0.1%) 1.7 KB
🟡 isValidCest 3.6 KB 3.6 KB +5 B (+0.1%) 852 B
🟡 isValidCfop 1.9 KB 1.9 KB +5 B (+0.3%) 693 B
🟡 isValidCnae 4.6 KB 4.6 KB +5 B (+0.1%) 1.9 KB
🟡 isValidNbs 3.5 KB 3.5 KB +5 B (+0.1%) 1.3 KB
🟡 isValidNcm 29.6 KB 29.6 KB +5 B (+0.0%) 9.0 KB
🟡 isValidServiceItem 1.1 KB 1.1 KB +5 B (+0.4%) 564 B
🟢 generateVoterId 926 B 922 B -4 B (-0.4%) 587 B
🟢 getCities 153.5 KB 153.5 KB -4 B (-0.0%) 49.3 KB
🟢 getCodeByMunicipalityName 153.6 KB 153.6 KB -4 B (-0.0%) 49.4 KB
🟢 getMunicipality 154.2 KB 154.2 KB -4 B (-0.0%) 49.8 KB
🟡 getStateByCep 3.6 KB 3.6 KB +4 B (+0.1%) 1.0 KB
🟢 getStateCapital 959 B 955 B -4 B (-0.4%) 638 B
🟢 getTimezoneByState 759 B 755 B -4 B (-0.5%) 342 B
🟢 isValidIe 5.2 KB 5.2 KB -4 B (-0.1%) 1.8 KB
🟢 isValidIE 5.2 KB 5.2 KB -4 B (-0.1%) 1.8 KB
🟡 isValidSuframa 607 B 611 B +4 B (+0.7%) 418 B
🟡 isValidVoterId 724 B 728 B +4 B (+0.6%) 433 B
All exports (198)
Export Base Head Δ gzip
⚪ GetAddressInfoByCepError 112 B 112 B 0 B 124 B
⚪ GetAddressInfoByCepNotFoundError 205 B 205 B 0 B 145 B
⚪ GetAddressInfoByCepServiceError 204 B 204 B 0 B 143 B
⚪ GetAddressInfoByCepValidationError 203 B 203 B 0 B 144 B
⚪ GetCepInfoByAddressError 112 B 112 B 0 B 124 B
⚪ GetCepInfoByAddressNotFoundError 205 B 205 B 0 B 145 B
⚪ GetCepInfoByAddressValidationError 203 B 203 B 0 B 144 B
🟢 addBusinessDays 9.5 KB 9.5 KB -17 B (-0.2%) 3.3 KB
🟡 capitalize 2.4 KB 2.5 KB +130 B (+5.3%) 1.2 KB
⚪ convertCurrencyToWords 2.0 KB 2.0 KB 0 B 1.0 KB
⚪ convertDateToWords 2.4 KB 2.4 KB 0 B 1.2 KB
🟡 convertLicensePlateToMercosul 573 B 632 B +59 B (+10.3%) 425 B
⚪ convertNumberToWords 1.6 KB 1.6 KB 0 B 819 B
🟢 differenceInBusinessDays 9.3 KB 9.3 KB -17 B (-0.2%) 3.3 KB
🟡 formatBoleto 649 B 668 B +19 B (+2.9%) 407 B
🟡 formatCEP 470 B 489 B +19 B (+4.0%) 348 B
🟡 formatCNPJ 620 B 639 B +19 B (+3.1%) 430 B
🟡 formatCPF 521 B 540 B +19 B (+3.6%) 376 B
🟡 formatCaepf 487 B 506 B +19 B (+3.9%) 358 B
🆕 formatCbo — 491 B new 350 B
🟡 formatCei 484 B 503 B +19 B (+3.9%) 356 B
🟡 formatCep 470 B 489 B +19 B (+4.0%) 348 B
🟡 formatCertidao 513 B 532 B +19 B (+3.7%) 361 B
🟡 formatCest 474 B 493 B +19 B (+4.0%) 350 B
🆕 formatCfop — 489 B new 349 B
🟡 formatCid10 459 B 478 B +19 B (+4.1%) 339 B
🟡 formatCnae 474 B 493 B +19 B (+4.0%) 352 B
🟡 formatCnh 517 B 536 B +19 B (+3.7%) 374 B
🟡 formatCno 488 B 507 B +19 B (+3.9%) 357 B
🟡 formatCnpj 620 B 639 B +19 B (+3.1%) 430 B
🟡 formatCns 483 B 506 B +23 B (+4.8%) 352 B
🟡 formatCpf 521 B 540 B +19 B (+3.6%) 376 B
🟡 formatCurrency 985 B 1023 B +38 B (+3.9%) 613 B
⚪ formatIban 267 B 267 B 0 B 228 B
⚪ formatIsbn 27.0 KB 27.0 KB 0 B 6.2 KB
🟡 formatLegalNature 470 B 489 B +19 B (+4.0%) 349 B
⚪ formatLicensePlate 361 B 361 B 0 B 270 B
🟡 formatNbs 462 B 496 B +34 B (+7.4%) 353 B
🟡 formatNcm 475 B 494 B +19 B (+4.0%) 352 B
🟡 formatNfeKey 601 B 724 B +123 B (+20.5%) 461 B
⚪ formatPassport 187 B 187 B 0 B 174 B
🟡 formatPhone 2.6 KB 2.6 KB +12 B (+0.4%) 1.2 KB
🟡 formatPis 521 B 540 B +19 B (+3.6%) 377 B
🟡 formatProcessoJuridico 490 B 509 B +19 B (+3.9%) 357 B
🟡 formatSuframa 476 B 499 B +23 B (+4.8%) 354 B
🟡 formatVoterId 523 B 542 B +19 B (+3.6%) 377 B
⚪ generateBoleto 1.4 KB 1.4 KB 0 B 779 B
⚪ generateCNPJ 843 B 843 B 0 B 523 B
🟢 generateCPF 702 B 694 B -8 B (-1.1%) 456 B
🔴 generateCep 130 B 1.3 KB +1.2 KB (+926.9%) 471 B
⚪ generateCnh 565 B 565 B 0 B 343 B
⚪ generateCnpj 843 B 843 B 0 B 523 B
🟢 generateCpf 702 B 694 B -8 B (-1.1%) 456 B
⚪ generateLegalNature 5.0 KB 5.0 KB 0 B 1.6 KB
🟡 generateLicensePlate 266 B 308 B +42 B (+15.8%) 243 B
⚪ generatePassport 226 B 226 B 0 B 191 B
⚪ generatePhone 670 B 670 B 0 B 419 B
⚪ generatePis 357 B 357 B 0 B 273 B
🟡 generatePixPayload 4.5 KB 4.7 KB +233 B (+5.1%) 2.1 KB
⚪ generateProcessoJuridico 821 B 821 B 0 B 523 B
⚪ generateRenavam 378 B 378 B 0 B 284 B
⚪ generateSuframa 451 B 451 B 0 B 335 B
🟢 generateVoterId 926 B 922 B -4 B (-0.4%) 587 B
🔴 getAddressInfoByCep 3.9 KB 5.3 KB +1.4 KB (+35.4%) 2.1 KB
🟡 getAreaCodeByMunicipalityCode 165.0 KB 165.1 KB +32 B (+0.0%) 52.2 KB
⚪ getAreaCodeInfo 3.1 KB 3.1 KB 0 B 989 B
⚪ getAreaCodesByState 814 B 814 B 0 B 463 B
⚪ getBankByCode 37.8 KB 37.8 KB 0 B 9.2 KB
⚪ getBankByIspb 37.8 KB 37.8 KB 0 B 9.2 KB
⚪ getBanks 37.6 KB 37.6 KB 0 B 9.0 KB
🟡 getBoletoInfo 2.4 KB 2.7 KB +255 B (+10.4%) 1.4 KB
🟡 getCbo 107.6 KB 107.6 KB +5 B (+0.0%) 24.8 KB
🟡 getCepInfoByAddress 2.0 KB 2.2 KB +158 B (+7.7%) 1.1 KB
⚪ getCertidaoInfo 1.0 KB 1.0 KB 0 B 594 B
🟡 getCest 111.6 KB 111.6 KB +5 B (+0.0%) 24.1 KB
🟡 getCfop 66.8 KB 66.8 KB +5 B (+0.0%) 5.3 KB
⚪ getCid10 988.3 KB 988.3 KB 0 B 123.6 KB
🟢 getCities 153.5 KB 153.5 KB -4 B (-0.0%) 49.3 KB
⚪ getClassTrib 50.0 KB 50.0 KB 0 B 9.0 KB
🟡 getCnae 86.6 KB 86.6 KB +5 B (+0.0%) 18.0 KB
⚪ getCnpjInfo 964 B 964 B 0 B 542 B
🟢 getCodeByMunicipalityName 153.6 KB 153.6 KB -4 B (-0.0%) 49.4 KB
⚪ getCpfInfo 994 B 994 B 0 B 614 B
⚪ getCstIbsCbs 967 B 967 B 0 B 534 B
🟡 getFormatLicensePlate 361 B 420 B +59 B (+16.3%) 301 B
⚪ getGtinInfo 821 B 821 B 0 B 558 B
🟢 getHolidays 8.4 KB 8.4 KB -17 B (-0.2%) 2.9 KB
⚪ getIbanInfo 752 B 752 B 0 B 492 B
⚪ getIsbnInfo 26.9 KB 26.9 KB 0 B 6.1 KB
🟡 getLegalNature 5.6 KB 5.7 KB +113 B (+2.0%) 1.9 KB
⚪ getLegalNatures 5.1 KB 5.1 KB 0 B 1.6 KB
⚪ getLegalNaturesByCategory 5.6 KB 5.6 KB 0 B 1.9 KB
⚪ getMunicipalities 153.7 KB 153.7 KB 0 B 49.5 KB
🟡 getMunicipalitiesByAreaCode 167.8 KB 167.8 KB +32 B (+0.0%) 52.8 KB
🟢 getMunicipality 154.2 KB 154.2 KB -4 B (-0.0%) 49.8 KB
⚪ getMunicipalityByCode 153.8 KB 153.8 KB 0 B 49.5 KB
🟡 getNbs 75.6 KB 75.6 KB +5 B (+0.0%) 11.6 KB
🟡 getNfeKeyInfo 2.2 KB 2.2 KB +6 B (+0.3%) 1.2 KB
⚪ getNfseKeyInfo 2.6 KB 2.6 KB 0 B 1.2 KB
🟡 getPixKeyInfo 2.5 KB 2.7 KB +225 B (+8.8%) 1.3 KB
🟡 getPixPayloadInfo 4.6 KB 4.8 KB +256 B (+5.5%) 2.2 KB
⚪ getRegions 255 B 255 B 0 B 165 B
🟡 getServiceItem 26.1 KB 26.1 KB +5 B (+0.0%) 8.1 KB
🟡 getStateByCep 3.6 KB 3.6 KB +4 B (+0.1%) 1.0 KB
⚪ getStateByIbgeCode 2.4 KB 2.4 KB 0 B 700 B
🟢 getStateCapital 959 B 955 B -4 B (-0.4%) 638 B
⚪ getStateCodeByName 2.3 KB 2.3 KB 0 B 649 B
⚪ getStateNameByCode 2.2 KB 2.2 KB 0 B 594 B
⚪ getStates 2.2 KB 2.2 KB 0 B 539 B
⚪ getStatesByRegion 2.3 KB 2.3 KB 0 B 602 B
🟢 getTimezoneByState 759 B 755 B -4 B (-0.5%) 342 B
🟢 isBusinessDay 8.8 KB 8.8 KB -17 B (-0.2%) 3.1 KB
🟢 isHoliday 8.7 KB 8.6 KB -17 B (-0.2%) 3.0 KB
🟡 isValidBankAccount 5.1 KB 5.1 KB +5 B (+0.1%) 1.9 KB
🟡 isValidBoleto 1.6 KB 1.8 KB +139 B (+8.4%) 970 B
⚪ isValidCEP 165 B 165 B 0 B 165 B
⚪ isValidCNPJ 796 B 796 B 0 B 447 B
⚪ isValidCPF 500 B 500 B 0 B 334 B
⚪ isValidCaepf 782 B 782 B 0 B 479 B
🟡 isValidCbo 6.7 KB 6.7 KB +5 B (+0.1%) 1.7 KB
⚪ isValidCei 714 B 714 B 0 B 472 B
⚪ isValidCep 165 B 165 B 0 B 165 B
⚪ isValidCertidao 758 B 758 B 0 B 462 B
🟡 isValidCest 3.6 KB 3.6 KB +5 B (+0.1%) 852 B
🟡 isValidCfop 1.9 KB 1.9 KB +5 B (+0.3%) 693 B
⚪ isValidCid10 26.2 KB 26.2 KB 0 B 6.8 KB
🟢 isValidClassTrib 1.8 KB 1.8 KB -44 B (-2.4%) 613 B
🟡 isValidCnae 4.6 KB 4.6 KB +5 B (+0.1%) 1.9 KB
⚪ isValidCnh 612 B 612 B 0 B 372 B
⚪ isValidCno 718 B 718 B 0 B 474 B
⚪ isValidCnpj 796 B 796 B 0 B 447 B
⚪ isValidCns 746 B 746 B 0 B 475 B
⚪ isValidCpf 500 B 500 B 0 B 334 B
⚪ isValidCreditCard 555 B 555 B 0 B 389 B
⚪ isValidCsosn 330 B 330 B 0 B 262 B
🟢 isValidCst 995 B 978 B -17 B (-1.7%) 548 B
⚪ isValidCstIbsCbs 894 B 894 B 0 B 505 B
🟡 isValidEmail 171 B 244 B +73 B (+42.7%) 198 B
⚪ isValidGtin 432 B 432 B 0 B 316 B
🟢 isValidIE 5.2 KB 5.2 KB -4 B (-0.1%) 1.8 KB
⚪ isValidIban 531 B 531 B 0 B 376 B
🟢 isValidIe 5.2 KB 5.2 KB -4 B (-0.1%) 1.8 KB
⚪ isValidIsbn 422 B 422 B 0 B 328 B
🟡 isValidLandlinePhone 717 B 886 B +169 B (+23.6%) 559 B
🟡 isValidLegalNature 741 B 911 B +170 B (+22.9%) 551 B
🟡 isValidLicensePlate 313 B 372 B +59 B (+18.8%) 280 B
🟡 isValidMobilePhone 758 B 927 B +169 B (+22.3%) 591 B
🟡 isValidNbs 3.5 KB 3.5 KB +5 B (+0.1%) 1.3 KB
🟡 isValidNcm 29.6 KB 29.6 KB +5 B (+0.0%) 9.0 KB
🟡 isValidNfeKey 1.6 KB 1.6 KB +6 B (+0.4%) 958 B
⚪ isValidNfseKey 2.2 KB 2.2 KB 0 B 1.1 KB
⚪ isValidPIS 418 B 418 B 0 B 316 B
⚪ isValidPassport 197 B 197 B 0 B 186 B
🟡 isValidPhone 1.8 KB 1.9 KB +58 B (+3.1%) 962 B
⚪ isValidPis 418 B 418 B 0 B 316 B
🟡 isValidPixKey 2.3 KB 2.5 KB +225 B (+9.6%) 1.2 KB
🟡 isValidPixPayload 4.1 KB 4.3 KB +256 B (+6.1%) 2.0 KB
⚪ isValidProcessoJuridico 711 B 711 B 0 B 432 B
🟡 isValidRegistroProfissional 1.3 KB 1.3 KB +41 B (+3.2%) 787 B
⚪ isValidRenavam 518 B 518 B 0 B 357 B
🟡 isValidServiceItem 1.1 KB 1.1 KB +5 B (+0.4%) 564 B
🔴 isValidServicePhone 706 B 1.0 KB +357 B (+50.6%) 573 B
🟡 isValidSuframa 607 B 611 B +4 B (+0.7%) 418 B
🟡 isValidVin 848 B 898 B +50 B (+5.9%) 600 B
🟡 isValidVoterId 724 B 728 B +4 B (+0.6%) 433 B
⚪ parseBoleto 247 B 247 B 0 B 215 B
⚪ parseCaepf 199 B 199 B 0 B 183 B
⚪ parseCbo 198 B 198 B 0 B 182 B
⚪ parseCei 199 B 199 B 0 B 183 B
⚪ parseCep 198 B 198 B 0 B 182 B
⚪ parseCertidao 199 B 199 B 0 B 183 B
⚪ parseCest 198 B 198 B 0 B 182 B
⚪ parseCfop 198 B 198 B 0 B 182 B
⚪ parseCid10 177 B 177 B 0 B 170 B
⚪ parseCnae 198 B 198 B 0 B 182 B
⚪ parseCnh 199 B 199 B 0 B 183 B
⚪ parseCno 199 B 199 B 0 B 183 B
⚪ parseCnpj 298 B 298 B 0 B 240 B
⚪ parseCns 199 B 199 B 0 B 184 B
⚪ parseCpf 199 B 199 B 0 B 183 B
⚪ parseCurrency 610 B 610 B 0 B 409 B
⚪ parseIban 227 B 227 B 0 B 202 B
⚪ parseIsbn 150 B 150 B 0 B 150 B
⚪ parseLegalNature 198 B 198 B 0 B 182 B
⚪ parseLicensePlate 177 B 177 B 0 B 169 B
🆕 parseNbs — 198 B new 181 B
⚪ parseNcm 198 B 198 B 0 B 182 B
⚪ parseNfeKey 412 B 412 B 0 B 312 B
⚪ parseNfseKey 253 B 253 B 0 B 219 B
⚪ parsePassport 177 B 177 B 0 B 169 B
🟡 parsePhone 370 B 497 B +127 B (+34.3%) 341 B
⚪ parsePis 199 B 199 B 0 B 183 B
⚪ parseProcessoJuridico 199 B 199 B 0 B 182 B
⚪ parseSuframa 198 B 198 B 0 B 181 B
⚪ parseVoterId 199 B 199 B 0 B 183 B
⚪ removeAccents 99 B 99 B 0 B 119 B
🟢 subBusinessDays 9.6 KB 9.6 KB -17 B (-0.2%) 3.4 KB
🟡 toStandardSchema 284 B 324 B +40 B (+14.1%) 254 B
How this is measured

Every export is imported alone into an esbuild consumer bundle (minified, tree-shaken) built from the head and from the base of this pull request; the sizes are the resulting bundles, gzip is their gzipped size. 🔴 marks a regression: a pre-existing export that grew more than 20% and more than 256 B, or the bundle importing every pre-existing export growing more than 5%. 🟡 is growth under the threshold, 🟢 a decrease, ⚪ no change, 🆕 an export that does not exist on the base (never a regression), 🗑️ an export that was removed. An intentional increase is accepted with the tree-shaking: accepted label.

Regression accepted through the "tree-shaking: accepted" label.

@codecov

codecov Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (1f82b56) to head (0ad6d4e).

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #615   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          239       247    +8     
  Lines         2430      2500   +70     
  Branches       714       733   +19     
=========================================
+ Hits          2430      2500   +70     
Flag Coverage Δ
node 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…the supported years

Past about year 275,000 the fator de vencimento resolves to an Invalid Date on both sides of the
comparison, and Bun and Deno do not treat two Invalid Dates as equal the way vitest does, so the
property drew its reference date from any year and failed on those runtimes. The date is now drawn
between 1900 and 2199.
…EP range alone

The cross-check of a provider's state only needs the state code of the CEP range, not the whole
State object getStateByCep builds, so the lookup no longer bundles the states table: about 3 KB
less in a bundle that imports getAddressInfoByCep alone. Same result for every CEP.
@hyanmandian
hyanmandian force-pushed the claude/final-validation branch from d12faad to 0df8ce1 Compare September 29, 2026 06:40
@hyanmandian hyanmandian added the tree-shaking: accepted The size increase reported by the tree-shaking check is intentional label Sep 29, 2026
@hyanmandian

Copy link
Copy Markdown
Member Author

Tree-shaking: the three flagged exports grow on purpose. generateCep (+1.2 KB) now draws inside the Correios ranges table so it never produces a CEP no state owns; isValidServicePhone (+0.35 KB) accepts a country code like isValidPhone does and rejects characters outside the mask; getAddressInfoByCep cross-checks the provider's answer against the CEP range (trimmed in 0df8ce1 to read only the range's state code, so it no longer bundles the states table). The next PR in the stack (#616) cuts the whole package by 25%.

normalizePhone now strips an explicit +55 or 0055, so the 13 digit value left for
"+555511987654321" was stripped a second time inside isValidMobilePhone and the key was
accepted, and getPixKeyInfo returned the malformed +555511987654321. A phone key is +55
followed by the 11 digit national number exactly once, so the national digits are now
required to be 11 long before they are validated as a mobile number.

Output change (wrong to correct, false in 2.4.0 as well):
isValidPixKey("+555511987654321"), "+55+5511987654321" and "0055+5511987654321" go from
true to false, and getPixKeyInfo of the same values from a phone key to null.
… zero as the same CEP

The cross-check of the answered CEP compared the provider's digits as they came, so a provider
answering 1310100 for 01310-100 was treated as not knowing the CEP, which 2.4.0 accepted. The
digits are now left padded to 8 before the comparison. A shortened CEP that is another CEP is
still rejected.

Output change (wrong to correct): a provider answer with the leading zero dropped resolves
instead of throwing GetAddressInfoByCepNotFoundError.
…ead of the daylight saving one

Decreto 8.112/2013 is the daylight saving decree, revoked in 2019, not the legal time norm. The
zones come from Decreto 2.784/1913 as amended by Lei 11.662/2008 and Lei 12.876/2013.
…ps to the next cycle

A slip due 3499 days before the reference date keeps its date and one due 3500 days before it is
read as the next cycle, about 9.6 years and not the 8 years the text said. The 15 years ahead half
never applies to an old cycle candidate, so it is dropped.
Replace the login-walled gov.br CPF URL with the Meu CPF page, drop the Goiás notice URL that
redirects to an unrelated page and the passport service URL that redirects to a service page (the
Polícia Federal FAQ stays), point the PT state-city guide at the renamed bundle section, remove a
duplicated isValidBoleto example, reword the isValidCst number 7 note, add the missing semicolons in
the generate import examples and spell hífens in PT.
… read as arrecadação

FEBRABAN product identifier 8 marks the arrecadação, so a cobrança bancária barcode of a bank code
from 800 to 899 (only 804 exists) is not accepted in barcode form, while its linha digitável is.
… the plate format alias

generateCep: the two ranges no state owns are 1.1% of all CEPs, about 1 in 90, not 1 in 80. The CEP
ranges note now says the Roraima block sits between the two Amazonas ranges and a Goiás range
between the two Distrito Federal ones. getLegalNature names the three retired codes without a
successor (2100, 3050, 3123). GenerateLicensePlateFormat is documented as an alias of
LicensePlateFormat.
… document the rest

Stryker's 100% threshold failed on 17 mutants. Nine are equivalent and now carry a
disable directive with the reason: the cache writes of findCodeIndex and formatCurrency,
the look-ahead bound of capitalize, the replacement of the XML Id prefix in formatNfeKey,
the format check of generateLicensePlate, the message of confirmAddress, the two guards
of toStandardSchema and the string guard of getCepInfoByAddress. The other eight are
killed by new tests: the providers of getAddressInfoByCep answering without a CEP for the
CEP 00000000, and the non-string values of isValidPhone and isValidServicePhone.

No behavior changes.
…any value

isValidPhone only ran the character check on strings, so an object whose toString gives "190x"
was read as the service number 190 while the string "190x" was rejected. The check now runs on
toStringSafe(value) for every input type.

Output change: an object (or array) whose string form holds a character other than digits,
whitespace and ()+.-/ goes from valid to invalid, like the same string. Numbers are unchanged,
their string form is digits only. isValidServicePhone(190) stays false while
isValidPhone(190, { accept: ["service"] }) stays true, as in 2.4.0.
The DDD index is a plain object, so a key such as "constructor" read an inherited member. A code
that is not an own key of the index now gives NaN instead of undefined or an inherited value.
Callers only pass 7 digit codes read from the same table, so no public result changes.
…rmatter cache

generateLicensePlate checked safeFormat === DEFAULT_FORMAT before the Mercosul letter index, but
index 4 of the old format is a digit, so the check was dead code and its directive hid three
killable mutants. The check and the directive are gone; Stryker scores 100% on the file without
any directive.

formatCurrency kept a directive above `formatters ??= new Map()`, a statement with no mutant, and
one above the cache hit return, which also hid the killable variant of the condition. The cache
is now read as `get(key) ?? store(...)`, so only the directive of the set call, whose removal
changes nothing observable, is left.

No behavior changes.
@hyanmandian
hyanmandian force-pushed the claude/final-validation branch from 909fa89 to 0ad6d4e Compare September 29, 2026 11:06
@hyanmandian
hyanmandian added this pull request to stack #618 September 29, 2026 12:49
@hyanmandian
hyanmandian merged commit d294baa into main Sep 29, 2026
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tree-shaking: accepted The size increase reported by the tree-shaking check is intentional

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant