Skip to content

feat(gtin): add isValidGtin and getGtinInfo - #563

Open
hyanmandian wants to merge 3 commits into
claude/get-state-by-cepfrom
claude/gtin
Open

hyanmandian wants to merge 3 commits into
claude/get-state-by-cepfrom
claude/gtin

Conversation

@hyanmandian

@hyanmandian hyanmandian commented Sep 19, 2026

Copy link
Copy Markdown
Member

Stacked on #562. This PR sits on top of #562 (getStateByCep) and merges after it, which in turn sits on #560, #559, #558 and #588. Its base branch is claude/get-state-by-cep, so the diff shown here is the GTIN change alone. Part of stack #591, with #561, #573, #567 and #565 on top of it.

What

Product barcode utilities, next to the NCM, CFOP and CST ones used for NF-e items: the NF-e requires a valid GTIN in cEAN and cEANTrib.

  • isValidGtin(value, options?): GTIN-8, GTIN-12, GTIN-13 and GTIN-14 with the GS1 modulo 10 check digit, and a lengths option to accept only some of them.
  • getGtinInfo(value): null, or the type, length, GS1 Prefix, whether the prefix is one of GS1 Brasil, whether it is in a Restricted Circulation Number range, and the check digit.
  • src/_internals/mod10 gains options.variant ("luhn" default, "gs1"), the way mod11 takes its variants. The GS1 rule differs from Luhn in the weight (3 instead of 2) and in adding the products as they are, so it is one function with an option, not a sibling. Existing callers are untouched.

API

type GtinLength = 8 | 12 | 13 | 14;
type GtinType = "GTIN-8" | "GTIN-12" | "GTIN-13" | "GTIN-14";
type GtinInfo = {
  type: GtinType;
  length: GtinLength;
  prefix: string;
  isBrazilian: boolean;
  isRestrictedCirculation: boolean;
  checkDigit: number;
};
type IsValidGtinOptions = { lengths?: GtinLength[] };

isValidGtin(value: string, options?: IsValidGtinOptions): boolean;
getGtinInfo(value: string): GtinInfo | null;
isValidGtin("7890000000017"); // true (GTIN-13, GS1 Brasil prefix)
isValidGtin("78912342"); // true (GTIN-8)
isValidGtin("17890000000014", { lengths: [8, 12, 13] }); // false
isValidGtin("7 890000 000017"); // false (digits only)
isValidGtin("SEM GTIN"); // false

getGtinInfo("17890000000014");
// { type: "GTIN-14", length: 14, prefix: "789", isBrazilian: true,
//   isRestrictedCirculation: false, checkDigit: 4 }
getGtinInfo("2000000000015")?.isRestrictedCirculation; // true
getGtinInfo("7890000000018"); // null

Decisions:

  • Strings only, digits only (surrounding whitespace aside). Leading zeros are part of a GTIN and decide its length, so a number is never read, and a mask is rejected instead of stripped.
  • type and length describe the value as written. The prefix is read from the 14 digit form the way SEFAZ tells: positions 7 to 9 when the first six are zeros (a GTIN-8), positions 2 to 4 otherwise. GS1 leaves the prefixes 0000001 to 0000099 unused for exactly that reason, so the two readings never collide.
  • Restricted circulation and special prefixes do not change the verdict; they are reported. The SEFAZ "Tabela Prefixo GS1" (rules I03-20 and I12-20) lists the restricted, ISSN, ISBN, refund and coupon ranges as valid, and they share the structure and the check digit. isRestrictedCirculation is derived from the ranges of the General Specifications, which are fixed by the standard.
  • isValidGtin is built on getGtinInfo rather than the other way round, which is the opposite of the get-boleto-info/get-iban-info/get-certidao-info pairs. Those validators need nothing the parser computes; this one does, since options.lengths filters on exactly the GtinLength the parse produces. Flipping the direction would leave getGtinInfo having to turn digits.length back into a GtinLength after isValidGtin already guaranteed it, which needs either a type assertion (no src file outside the tests has one) or a second GTIN_LENGTHS.find with an undefined branch that nothing can reach, and so nothing can cover or kill a mutant in. The cost of keeping the sound direction is 112 B and one object allocation per isValidGtin call. The constants stay in src/get-gtin-info/constants.ts, the folder that owns them, as get-certidao-info does for is-valid-certidao.
  • The prefix is not checked against the list of Member Organisations. The SEFAZ spreadsheet is a snapshot (it has no row for prefixes the General Specifications already call issuable, such as 140 to 199), GS1 keeps assigning ranges, and a copy would turn down valid numbers as it ages. It is not a dataset this library can refresh from a machine readable official source either.

Sources

  • GS1 General Specifications, release 26.0: section 7.9.1, tables 7-8 and 7-9 (weights 3 and 1 from the right, "subtract sum from nearest equal or higher multiple of ten", the 18 digit worked example used in the mod10 test); table 1-4 (GS1 Prefix ranges: 02, 04, 20 to 29 and 0000000 for Restricted Circulation Numbers, 0000001 to 0000099 unused to avoid collision with GTIN-8, 952 for demonstrations); table 1-5 (GS1-8 Prefixes: 000 to 099 and 200 to 299 restricted); section 1.2.2.2.1 (RCNs are not globally unique). The 952 numbers used as vectors are published there as a GLN (9521234500018) and a GRAI (09524141234564); section 7.9.1 is "identical for all fixed length numeric GS1 data structures", so they carry the same check digit and are valid GTIN vectors.
  • GS1, "How to calculate a check digit manually": same algorithm per GTIN length, and the 6291041500213 worked example.
  • SEFAZ Nota Técnica 2021.003 v1.30, Validação GTIN (replaces NT 2017.001): fields I03 cEAN and I12 cEANTrib take GTIN-8, 12, 13 or 14 or the literal "SEM GTIN"; rules I03-10 and I12-10 (check digit, rejections 611 and 612); rules I03-20 and I12-20 (prefix against the table below); rules 9I03-10 and 9I12-10 ("prefixo do Brasil (iniciado em 789 ou 790)", looked up in the Cadastro Centralizado de GTIN).
  • "Tabela Prefixo GS1" of the Portal da NF-e (xlsx): how to normalize to 14 digits and where the prefix sits, the row "789 to 790, GS1 Brasil", and the special ranges flagged as valid.

No third party implementation was used. gs1.org and the Portal da NF-e answer 403 or a redirect loop to a plain fetch; both open in a browser (curl with a browser user agent and a cookie jar also works).

Verification

  • npm run check: pass
  • npm run test -- --run: 186 files, 6192 passed
  • npm run test:coverage: 100% statements, branches, functions and lines
  • npm run build: pass (attw and publint clean)
  • npm run check:api:update: report updated and committed (additions only)
  • npm run check:unused: pass
  • npm run check:duplication: 0 clones
  • npm run check:tree-shaking: pass; isValidGtin 1711 B (1046 B min), getGtinInfo 1599 B (1003 B min)
  • npm run check:commits: pass
  • npm run test:mutation on get-gtin-info.ts, is-valid-gtin.ts and mod10.ts: 100% (one mod10 mutant killed by timeout)
  • npm run test:bun and npm run test:deno: 6192 passed, 0 failed
  • npm run build:docs and npm run build:jsr: run, output committed (jsr.json gains ./is-valid-gtin and ./get-gtin-info)
  • Not run: the browser test scripts and the full Stryker run.

Review follow-ups

  • The check digit paragraphs now say the sum is subtracted from the "nearest equal or higher multiple of ten", the wording of table 7-8, instead of "the next multiple of ten", which read literally gives 10 for a sum that already is a multiple of ten.
  • Three test titles credited GS1 numbers to the wrong key and were reworded: 9521234500018 is published as a GLN, 09524141234564 as a GRAI, and 061414112345 is the 12 digit body of a GTIN-13 built on the U.P.C. Company Prefix 614141, not a published GTIN-12. The vectors themselves are correct and unchanged.
  • The isRestrictedCirculation row of both docs pages now also names the GS1 Prefix 0000000 of table 1-4, which the code already flags through the GS1-8 reading and which get-gtin-info.test.ts already covers, and the prefix description states the condition the code tests (the first six digits of the 14 digit form are zeros) instead of "GS1-8 Prefix for a GTIN-8".

Rebase onto #562

Rebased from main onto claude/get-state-by-cep, so this branch now carries #588, #558, #559, #560 and #562 underneath it. Conflicts resolved:

  • docs/llms.txt and docs/llms-full.txt are no longer tracked (they are generated now), so both were git rm-ed.
  • The GTIN family of docs/utilities.md and docs/pt-br/utilities.md was ported into the new per-utility format of feat: Standard Schema wrapper, JSR, pkg.pr.new, docs previews and a playground #556: a short paragraph per function, a bullet list for the options, the structures and the edge cases, the GtinInfo field table kept as a table, the javascript blocks, and one shared Source: line at the end of the family carrying the GS1 and SEFAZ links that used to be inline.
  • src/index.ts and src/index.test.ts kept strictly alphabetical.
  • jsr.json (new on main) regenerated with npm run build:jsr, and reports/api/brazilian-utils.api.md with npm run check:api:update. Both are folded into the commits that own them, with no separate "regenerate" commit.

Re-verified on the rebased branch: npm run check, npm run test:coverage (100% statements, branches, functions and lines), npm run build, npm run check:unused, npm run check:duplication and npm run check:commits all pass.

Open points

  • The all zeros value ("00000000", "0000000000000") has a correct check digit and sits in a restricted range, so it is accepted and flagged isRestrictedCirculation. Neither GS1 nor the NT states a rule against it, so none was added.
  • A 14 digit value that starts with 0 is, for GS1, a shorter GTIN in the 14 digit form (NT rule 9I03-40 also reads GTIN-14 as cEAN > 09999999999999). It is reported with the length it was written with ("GTIN-14", 14); only the prefix follows the normalized reading.
  • No country or Member Organisation name is returned, only isBrazilian, for the snapshot reason above.
  • isBrazilian on a GTIN-8 rests on SEFAZ, not on GS1. Table 1-5 of the General Specifications only says the GS1-8 Prefixes 300 to 951 are "used to issue GTIN-8s"; it does not publish the per Member Organisation split of that space. The "Tabela Prefixo GS1" has a single prefix sheet whose instructions cover both readings, and its 789 to 790 row is what this follows, so isBrazilian is what SEFAZ would compute, which is what the utility claims.
  • The commits carry no Signed-off-by line, matching the history of main; add it on merge if the DCO check asks for it.

Summary by CodeRabbit

  • New Features
    • Added GTIN validation for GTIN-8, GTIN-12, GTIN-13, and GTIN-14, including GS1 check-digit verification.
    • Added GTIN information lookup with type, length, prefix, check digit, Brazilian origin, and restricted-circulation details.
    • Added optional filtering by accepted GTIN lengths.
  • Documentation
    • Added English and Portuguese documentation with validation rules and usage examples.

@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 21935d84-b42d-464e-9498-98717b832923

📥 Commits

Reviewing files that changed from the base of the PR and between 86268ba and 3b5340a.

📒 Files selected for processing (6)
  • docs/pt-br/utilities.md
  • docs/utilities.md
  • jsr.json
  • reports/api/brazilian-utils.api.md
  • src/index.test.ts
  • src/index.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

This change adds GS1 modulo-10 support, GTIN parsing and classification, length-filtered validation, public exports, package entries, API reports, tests, and English and Portuguese documentation.

Changes

GTIN utilities

Layer / File(s) Summary
GS1 modulo-10 support
src/_internals/mod10/mod10.ts, src/_internals/mod10/mod10.test.ts
mod10 now supports luhn and gs1 variants. Tests cover both variants and GS1 examples.
GTIN parsing and classification
src/get-gtin-info/*
Added getGtinInfo, GTIN metadata types, supported-length constants, prefix classification, normalization, and parser tests.
GTIN validation API
src/is-valid-gtin/*
Added isValidGtin with optional length filtering. Tests cover examples, properties, invalid inputs, and type contracts.
Public API and documentation
src/index.ts, src/index.test.ts, jsr.json, reports/api/*, docs/utilities.md, docs/pt-br/utilities.md
Exported the GTIN APIs and types, added package entry points and API declarations, and documented both functions in English and Portuguese.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant isValidGtin
  participant getGtinInfo
  participant mod10
  Caller->>isValidGtin: submit value and optional lengths
  isValidGtin->>getGtinInfo: parse and classify value
  getGtinInfo->>mod10: validate GS1 check digit
  mod10-->>getGtinInfo: return check result
  getGtinInfo-->>isValidGtin: return metadata or null
  isValidGtin-->>Caller: return boolean
Loading

Merge Risk: ⚪ Minimal · up to 3b534

The PR adds GTIN validation and metadata APIs with documented public exports and coverage for supported formats. No merge-blocking production risk is identified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 9…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the two main GTIN utilities added by the pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Tree-shaking report

No size regression. 9 grew, 9 new out of 165 exports.

Base Head Δ
Pre-existing exports, all imported 649.3 KB 649.4 KB (gzip 166.5 KB) +65 B (+0.0%)
Full import 649.3 KB 652.0 KB (gzip 167.1 KB) +2.7 KB (+0.4%)
Exports 156 165 +9

What changed (18)

Export Base Head Δ gzip
🆕 getStateByCep 4.5 KB new 1.5 KB
🆕 getCnpjInfo 1.8 KB new 1012 B
🆕 isValidGtin 1.7 KB new 1.0 KB
🆕 getCpfInfo 1.7 KB new 999 B
🆕 getGtinInfo 1.6 KB new 1003 B
🆕 isValidSuframa 1.4 KB new 884 B
🆕 generateSuframa 1.3 KB new 809 B
🆕 formatSuframa 1.3 KB new 779 B
🆕 parseSuframa 1002 B new 620 B
🟡 generateBoleto 2.1 KB 2.1 KB +37 B (+1.7%) 1.2 KB
🟡 getBoletoInfo 3.1 KB 3.1 KB +37 B (+1.2%) 1.6 KB
🟡 isValidBankAccount 7.4 KB 7.4 KB +37 B (+0.5%) 2.9 KB
🟡 isValidBoleto 2.4 KB 2.4 KB +37 B (+1.5%) 1.3 KB
🟡 isValidCreditCard 1.4 KB 1.4 KB +37 B (+2.6%) 896 B
🟡 isValidIe 5.7 KB 5.7 KB +37 B (+0.6%) 2.2 KB
🟡 isValidIE 5.7 KB 5.7 KB +37 B (+0.6%) 2.2 KB
🟡 generateCnpj 1.6 KB 1.6 KB +4 B (+0.2%) 968 B
🟡 generateCNPJ 1.6 KB 1.6 KB +4 B (+0.2%) 968 B
All exports (165)
Export Base Head Δ gzip
GetAddressInfoByCepError 966 B 966 B 0 B 600 B
GetAddressInfoByCepNotFoundError 1.0 KB 1.0 KB 0 B 618 B
GetAddressInfoByCepServiceError 1.0 KB 1.0 KB 0 B 617 B
GetAddressInfoByCepValidationError 1.0 KB 1.0 KB 0 B 620 B
GetCepInfoByAddressError 966 B 966 B 0 B 600 B
GetCepInfoByAddressNotFoundError 1.0 KB 1.0 KB 0 B 618 B
GetCepInfoByAddressValidationError 1.0 KB 1.0 KB 0 B 620 B
addBusinessDays 6.8 KB 6.8 KB 0 B 2.8 KB
capitalize 2.5 KB 2.5 KB 0 B 1.3 KB
convertCurrencyToWords 2.8 KB 2.8 KB 0 B 1.5 KB
convertDateToWords 3.2 KB 3.2 KB 0 B 1.7 KB
convertLicensePlateToMercosul 1.3 KB 1.3 KB 0 B 807 B
convertNumberToWords 2.4 KB 2.4 KB 0 B 1.3 KB
differenceInBusinessDays 6.9 KB 6.9 KB 0 B 2.9 KB
formatBoleto 1.4 KB 1.4 KB 0 B 837 B
formatCEP 1.2 KB 1.2 KB 0 B 778 B
formatCNPJ 1.4 KB 1.4 KB 0 B 855 B
formatCPF 1.3 KB 1.3 KB 0 B 807 B
formatCaepf 1.3 KB 1.3 KB 0 B 787 B
formatCei 1.3 KB 1.3 KB 0 B 785 B
formatCep 1.2 KB 1.2 KB 0 B 778 B
formatCertidao 1.3 KB 1.3 KB 0 B 789 B
formatCnae 1.2 KB 1.2 KB 0 B 782 B
formatCnh 1.3 KB 1.3 KB 0 B 780 B
formatCno 1.3 KB 1.3 KB 0 B 786 B
formatCnpj 1.4 KB 1.4 KB 0 B 855 B
formatCns 1.3 KB 1.3 KB 0 B 780 B
formatCpf 1.3 KB 1.3 KB 0 B 807 B
formatCurrency 1.8 KB 1.8 KB 0 B 1.0 KB
formatIban 1.1 KB 1.1 KB 0 B 696 B
formatLegalNature 1.2 KB 1.2 KB 0 B 777 B
formatLicensePlate 1.2 KB 1.2 KB 0 B 738 B
formatNcm 1.2 KB 1.2 KB 0 B 780 B
formatNfeKey 1.3 KB 1.3 KB 0 B 783 B
formatPassport 1.0 KB 1.0 KB 0 B 643 B
formatPhone 2.8 KB 2.8 KB 0 B 1.5 KB
formatPis 1.3 KB 1.3 KB 0 B 781 B
formatProcessoJuridico 1.3 KB 1.3 KB 0 B 785 B
🆕 formatSuframa 1.3 KB new 779 B
formatVoterId 1.3 KB 1.3 KB 0 B 821 B
🟡 generateBoleto 2.1 KB 2.1 KB +37 B (+1.7%) 1.2 KB
🟡 generateCNPJ 1.6 KB 1.6 KB +4 B (+0.2%) 968 B
generateCPF 1.4 KB 1.4 KB 0 B 878 B
generateCep 984 B 984 B 0 B 610 B
generateCnh 1.4 KB 1.4 KB 0 B 829 B
🟡 generateCnpj 1.6 KB 1.6 KB +4 B (+0.2%) 968 B
generateCpf 1.4 KB 1.4 KB 0 B 878 B
generateLegalNature 5.9 KB 5.9 KB 0 B 2.1 KB
generateLicensePlate 1.1 KB 1.1 KB 0 B 692 B
generatePassport 1.1 KB 1.1 KB 0 B 656 B
generatePhone 1.5 KB 1.5 KB 0 B 900 B
generatePis 1.2 KB 1.2 KB 0 B 744 B
generatePixPayload 6.3 KB 6.3 KB 0 B 2.8 KB
generateProcessoJuridico 1.4 KB 1.4 KB 0 B 870 B
generateRenavam 1.2 KB 1.2 KB 0 B 760 B
🆕 generateSuframa 1.3 KB new 809 B
generateVoterId 1.7 KB 1.7 KB 0 B 1021 B
getAddressInfoByCep 4.1 KB 4.1 KB 0 B 1.9 KB
getAreaCodeInfo 3.9 KB 3.9 KB 0 B 1.4 KB
getAreaCodesByState 1.6 KB 1.6 KB 0 B 917 B
getBankByCode 38.6 KB 38.6 KB 0 B 9.8 KB
getBankByIspb 38.6 KB 38.6 KB 0 B 9.8 KB
getBanks 38.4 KB 38.4 KB 0 B 9.6 KB
🟡 getBoletoInfo 3.1 KB 3.1 KB +37 B (+1.2%) 1.6 KB
getCbo 119.1 KB 119.1 KB 0 B 30.7 KB
getCepInfoByAddress 2.7 KB 2.7 KB 0 B 1.4 KB
getCertidaoInfo 1.8 KB 1.8 KB 0 B 1.0 KB
getCfop 68.9 KB 68.9 KB 0 B 6.9 KB
getCities 154.3 KB 154.3 KB 0 B 49.9 KB
getCnae 93.9 KB 93.9 KB 0 B 21.2 KB
🆕 getCnpjInfo 1.8 KB new 1012 B
🆕 getCpfInfo 1.7 KB new 999 B
getFormatLicensePlate 1.1 KB 1.1 KB 0 B 692 B
🆕 getGtinInfo 1.6 KB new 1003 B
getHolidays 6.1 KB 6.1 KB 0 B 2.6 KB
getIbanInfo 1.6 KB 1.6 KB 0 B 955 B
getLegalNature 6.3 KB 6.3 KB 0 B 2.3 KB
getLegalNatures 5.9 KB 5.9 KB 0 B 2.1 KB
getLegalNaturesByCategory 6.5 KB 6.5 KB 0 B 2.4 KB
getMunicipalities 156.4 KB 156.4 KB 0 B 50.3 KB
getMunicipality 154.9 KB 154.9 KB 0 B 50.3 KB
getMunicipalityByCode 156.5 KB 156.5 KB 0 B 50.4 KB
getNfeKeyInfo 2.7 KB 2.7 KB 0 B 1.5 KB
getPixKeyInfo 4.5 KB 4.5 KB 0 B 2.0 KB
getPixPayloadInfo 2.9 KB 2.9 KB 0 B 1.4 KB
🆕 getStateByCep 4.5 KB new 1.5 KB
getStateByIbgeCode 3.2 KB 3.2 KB 0 B 1.1 KB
getStateCodeByName 3.2 KB 3.2 KB 0 B 1.1 KB
getStateNameByCode 3.1 KB 3.1 KB 0 B 1.0 KB
getStates 3.0 KB 3.0 KB 0 B 1017 B
getTimezoneByState 1.6 KB 1.6 KB 0 B 809 B
isBusinessDay 6.5 KB 6.5 KB 0 B 2.7 KB
isHoliday 6.4 KB 6.4 KB 0 B 2.7 KB
🟡 isValidBankAccount 7.4 KB 7.4 KB +37 B (+0.5%) 2.9 KB
🟡 isValidBoleto 2.4 KB 2.4 KB +37 B (+1.5%) 1.3 KB
isValidCEP 984 B 984 B 0 B 610 B
isValidCNPJ 1.6 KB 1.6 KB 0 B 914 B
isValidCPF 1.3 KB 1.3 KB 0 B 805 B
isValidCaepf 1.5 KB 1.5 KB 0 B 913 B
isValidCbo 119.2 KB 119.2 KB 0 B 30.7 KB
isValidCei 1.5 KB 1.5 KB 0 B 898 B
isValidCep 984 B 984 B 0 B 610 B
isValidCertidao 1.6 KB 1.6 KB 0 B 938 B
isValidCfop 68.9 KB 68.9 KB 0 B 6.9 KB
isValidCnae 94.0 KB 94.0 KB 0 B 21.2 KB
isValidCnh 1.4 KB 1.4 KB 0 B 856 B
isValidCno 1.5 KB 1.5 KB 0 B 900 B
isValidCnpj 1.6 KB 1.6 KB 0 B 914 B
isValidCns 1.5 KB 1.5 KB 0 B 925 B
isValidCpf 1.3 KB 1.3 KB 0 B 805 B
🟡 isValidCreditCard 1.4 KB 1.4 KB +37 B (+2.6%) 896 B
isValidCsosn 1.2 KB 1.2 KB 0 B 737 B
isValidCst 1.8 KB 1.8 KB 0 B 1.0 KB
isValidEmail 1.0 KB 1.0 KB 0 B 622 B
🆕 isValidGtin 1.7 KB new 1.0 KB
🟡 isValidIE 5.7 KB 5.7 KB +37 B (+0.6%) 2.2 KB
isValidIban 1.3 KB 1.3 KB 0 B 836 B
🟡 isValidIe 5.7 KB 5.7 KB +37 B (+0.6%) 2.2 KB
isValidLandlinePhone 1.5 KB 1.5 KB 0 B 932 B
isValidLegalNature 5.8 KB 5.8 KB 0 B 2.1 KB
isValidLicensePlate 1.1 KB 1.1 KB 0 B 702 B
isValidMobilePhone 1.6 KB 1.6 KB 0 B 971 B
isValidNcm 114.2 KB 114.2 KB 0 B 24.6 KB
isValidNfeKey 2.7 KB 2.7 KB 0 B 1.5 KB
isValidPIS 1.2 KB 1.2 KB 0 B 784 B
isValidPassport 1.0 KB 1.0 KB 0 B 654 B
isValidPhone 2.6 KB 2.6 KB 0 B 1.3 KB
isValidPis 1.2 KB 1.2 KB 0 B 784 B
isValidPixKey 4.6 KB 4.6 KB 0 B 2.1 KB
isValidPixPayload 2.9 KB 2.9 KB 0 B 1.5 KB
isValidProcessoJuridico 1.3 KB 1.3 KB 0 B 787 B
isValidRegistroProfissional 1.6 KB 1.6 KB 0 B 964 B
isValidRenavam 1.3 KB 1.3 KB 0 B 814 B
isValidServicePhone 1.5 KB 1.5 KB 0 B 846 B
🆕 isValidSuframa 1.4 KB new 884 B
isValidVin 1.6 KB 1.6 KB 0 B 995 B
isValidVoterId 1.6 KB 1.6 KB 0 B 900 B
parseBoleto 1020 B 1020 B 0 B 634 B
parseCaepf 1003 B 1003 B 0 B 621 B
parseCbo 1002 B 1002 B 0 B 620 B
parseCei 1003 B 1003 B 0 B 619 B
parseCep 1002 B 1002 B 0 B 620 B
parseCertidao 1003 B 1003 B 0 B 620 B
parseCfop 1002 B 1002 B 0 B 620 B
parseCnae 1002 B 1002 B 0 B 620 B
parseCnh 1003 B 1003 B 0 B 621 B
parseCno 1003 B 1003 B 0 B 619 B
parseCnpj 1.1 KB 1.1 KB 0 B 669 B
parseCns 1003 B 1003 B 0 B 621 B
parseCpf 1003 B 1003 B 0 B 621 B
parseCurrency 1.4 KB 1.4 KB 0 B 881 B
parseIban 1.0 KB 1.0 KB 0 B 638 B
parseLegalNature 1002 B 1002 B 0 B 620 B
parseLicensePlate 1.0 KB 1.0 KB 0 B 638 B
parseNcm 1002 B 1002 B 0 B 620 B
parseNfeKey 1.0 KB 1.0 KB 0 B 659 B
parsePassport 1.0 KB 1.0 KB 0 B 637 B
parsePhone 1.1 KB 1.1 KB 0 B 707 B
parsePis 1003 B 1003 B 0 B 621 B
parseProcessoJuridico 1003 B 1003 B 0 B 621 B
🆕 parseSuframa 1002 B new 620 B
parseVoterId 1.0 KB 1.0 KB 0 B 649 B
removeAccents 953 B 953 B 0 B 593 B
subBusinessDays 6.9 KB 6.9 KB 0 B 2.9 KB
toStandardSchema 1.1 KB 1.1 KB 0 B 713 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.

@codecov

codecov Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (2b00546) to head (3b5340a).

Additional details and impacted files
@@                    Coverage Diff                    @@
##           claude/get-state-by-cep      #563   +/-   ##
=========================================================
  Coverage                   100.00%   100.00%           
=========================================================
  Files                          193       195    +2     
  Lines                         2107      2131   +24     
  Branches                       621       630    +9     
=========================================================
+ Hits                          2107      2131   +24     
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.

@hyanmandian

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@hyanmandian

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

GS1 keys use a modulo 10 that differs from the Luhn one only in the weight (3 instead of 2) and
in adding the products as they are instead of their digits (GS1 General Specifications, section
7.9.1). It comes as `options.variant`, the way `mod11` takes its variants, with `"luhn"` as the
default so the boleto, credit card, bank account and IE callers are untouched.
The NF-e requires a valid GTIN in `cEAN` and `cEANTrib` (rules I03-10 and I12-10 of SEFAZ NT
2021.003, rejections 611 and 612), so the product barcode sits next to the NCM, CFOP and CST
utilities. `isValidGtin(value, options?)` covers GTIN-8, GTIN-12, GTIN-13 and GTIN-14 with the GS1
modulo 10 check digit and takes `lengths` to accept only some of them. `getGtinInfo(value)`
returns the type, the length, the three digit GS1 Prefix read from the 14 digit form the way the
"Tabela Prefixo GS1" of the Portal da NF-e tells, whether it is one of GS1 Brasil (789, 790),
whether it falls in a Restricted Circulation Number range of the General Specifications (tables
1-4 and 1-5) and the check digit.

The prefix never changes the verdict: the SEFAZ table lists the restricted, ISSN, ISBN and coupon
ranges as valid, and a copy of the Member Organisation list would turn down valid numbers as GS1
assigns new ranges. The vectors are the examples GS1 publishes (6291041500213, 9521234500018,
09524141234564, 061414112345) and synthetic values worked out by hand.
The check digit paragraphs said the sum is subtracted from "the next
multiple of ten". Read literally that gives 10 for a sum that already is
a multiple of ten. Table 7-8 of the General Specifications says "nearest
equal or higher multiple of ten", which is what the code does, so use
that phrasing in both JSDoc blocks and in both docs pages.

Three test titles credited GS1 numbers to the wrong key: 9521234500018
appears in the General Specifications as a GLN, 09524141234564 as a
GRAI, and 061414112345 is the 12 digit body of a GTIN-13 built on the
U.P.C. Company Prefix 614141, not a published GTIN-12. All three are
valid vectors, since section 7.9.1 is the same rule for every fixed
length GS1 key, so only the titles change.

The isRestrictedCirculation row of both docs listed three restricted
ranges of table 1-4 and left out the GS1 Prefix 0000000, which the code
already flags through the GS1-8 reading and which the tests already
cover. Say so in the row, and widen the prefix description from "GS1-8
Prefix for a GTIN-8" to the condition the code tests: the first six
digits of the 14 digit form are zeros.
@vercel

vercel Bot commented Sep 22, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
brazilian-utils Ready Ready Preview Sep 22, 2026 5:04am UTC

@hyanmandian
hyanmandian changed the base branch from main to claude/get-state-by-cep September 22, 2026 05:04
@pkg-pr-new

pkg-pr-new Bot commented Sep 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 3b5340a

@hyanmandian

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

This branch was successfully deployed

1 active deployment
Preview 3b5340a9 Deployed Sep 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant