@br-geo-kit/cep
v1.0.0
Published
Resolve Brazilian CEPs through a chain of providers, with caching, fallback and offline enrichment
Maintainers
Readme
@br-geo-kit/cep
Resolve a Brazilian CEP through a chain of providers that survives any one of them going down.
pnpm add @br-geo-kit/cep @br-geo-kit/provider-brasilapi @br-geo-kit/provider-viacepimport { createCep, memoryCache } from '@br-geo-kit/cep'
import { brasilapi } from '@br-geo-kit/provider-brasilapi'
import { viacep } from '@br-geo-kit/provider-viacep'
import { ibge } from '@br-geo-kit/ibge'
const cep = createCep({
providers: [brasilapi(), viacep()],
cache: memoryCache(),
territory: ibge,
})
await cep.lookup('01310-100')The one distinction everything rests on
| Result | Meaning |
| --- | --- |
| an Address | the CEP exists |
| null | every provider was asked, and none of them has it |
| throws AllProvidersFailedError | nobody could be reached |
Most hand-rolled integrations collapse the last two, and the bug that follows is always the same: a rate limit or an outage reaches the user as "this address does not exist", so somebody with a perfectly valid address retypes it until they give up.
try {
const address = await cep.lookup(input)
if (address === null) {
// no such CEP — ask the user to check it
}
} catch (error) {
// nobody answered — ask them to try again, do not blame the address
}The canonical address
Every provider is normalized into one shape. ViaCEP says
logradouro/localidade/uf, BrasilAPI says street/city/state,
AwesomeAPI says something else again.
{
cep: '01310100', // always eight digits, no mask
street: 'Avenida Paulista',
complement: 'de 612 a 1510 - lado par',
neighborhood: 'Bela Vista',
city: 'São Paulo',
state: 'SP',
ibge: { city: '3550308', state: '35' },
ddd: '11',
timezone: 'America/Sao_Paulo',
coordinates: { latitude: -23.5617698, longitude: -46.6553299 },
source: 'brasilapi',
cached: false
}A field is null when the source did not provide it. Never a guess,
never an empty string — a CEP único covers a whole small municipality
and legitimately has no street.
Input is accepted in every shape a CEP arrives in: 01310-100,
01310100, 01.310-100, and the number 1310100 a spreadsheet
produced by dropping the leading zero.
Options
createCep({
providers: [brasilapi(), viacep()], // required, in order of preference
strategy: 'fallback', // | 'race' | 'consensus'
timeoutMs: 3000, // per provider
cache: memoryCache(), // default: nothing is cached
territory: ibge, // offline enrichment + range check
onWarning: (warning) => log(warning),
fetch: myInstrumentedFetch,
})strategy — fallback asks one at a time, in order: one request in
the common case, and the default. race asks all at once and takes the
first answer, trading N times the traffic for the fastest provider's
latency. consensus asks all, waits for all, returns the majority and
reports every disagreement — the only one that catches a provider that
is confidently wrong rather than merely down.
territory — pass ibge from @br-geo-kit/ibge and the kit fills
the area code, the IBGE code and the time zone that a provider left out,
skips the network for a CEP in an unallocated block, and warns when an
answer's state contradicts the CEP's block. This package does not
depend on @br-geo-kit/ibge; it declares the three-method interface it
needs, so the 730 KB dataset never lands in a bundle that did not ask
for it.
cache — turn it on. No public CEP service publishes a rate limit,
which means the limit exists and you will find it in production. See
caching.
Warnings
Everything the resolver noticed but did not treat as fatal:
onWarning: (warning) => {
switch (warning.kind) {
case 'provider-failed': // the chain recovered, but note it
case 'provider-disagreement': // consensus only
case 'state-range-mismatch': // the answer contradicts the CEP block
}
}Without provider-failed, a provider that has been down for a week is
invisible for as long as the one behind it keeps working.
provider-disagreement reports only genuine contradictions: a provider
that returns null for a field has not dissented, it just does not
carry that field, and coordinates are compared by distance rather than
equality. Otherwise every record produces a warning and nobody reads
them.
state-range-mismatch is not hypothetical: CEP 99999-999 does not
exist in the Correios' base, but the open-cep base behind BrasilAPI
and OpenCEP has a record placing it in Sarandi/PR — while the CEP itself
sits inside Rio Grande do Sul's block.
Reverse lookup
await cep.reverse({ state: 'SP', city: 'São Paulo', street: 'Paulista' })Only providers that declare the capability are asked, and ViaCEP is the
only public one that has it. Returns [] rather than failing when no
provider in the chain can do it.
Documentation
Providers and strategies · Caching · API conventions · Browser and React
Licence
MIT
