@eusilvio/cep-lookup 2.4.0 → 2.5.1
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 -47
- package/README.md +12 -6
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/providers/index.cjs +1 -1
- package/dist/providers/index.mjs +1 -1
- package/dist/src/cache/index.d.ts +14 -17
- package/dist/src/cache/index.js +35 -20
- package/dist/src/data/ddd-by-state.d.ts +2 -0
- package/dist/src/data/ddd-by-state.js +12 -0
- package/dist/src/errors.d.ts +23 -0
- package/dist/src/errors.js +46 -0
- package/dist/src/index.d.ts +12 -18
- package/dist/src/index.js +70 -33
- package/dist/src/providers/index.d.ts +1 -0
- package/dist/src/providers/index.js +1 -0
- package/dist/src/providers/opencep.d.ts +10 -0
- package/dist/src/providers/opencep.js +32 -0
- package/dist/src/providers/viacep.js +2 -0
- package/dist/src/types.d.ts +12 -2
- package/package.json +3 -2
package/LICENSE
CHANGED
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
This project was developed with the philosophy that knowledge and tools should be accessible to all. I believe that by sharing our work freely, we can empower other creators to build incredible things.
|
|
4
|
-
|
|
5
|
-
That's why I chose the MIT License for this project. In simple terms, this means you have complete freedom to use, modify, and distribute this code, even for commercial purposes, with a single condition: you must keep the original copyright and license notice included.
|
|
6
|
-
|
|
7
|
-
Thank you for your interest and contributions!
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Licença do Projeto
|
|
12
|
-
|
|
13
|
-
Este projeto foi desenvolvido com a filosofia de que o conhecimento e as ferramentas devem ser acessíveis a todos. Acredito que, ao compartilhar nosso trabalho livremente, podemos capacitar outros criadores a construir coisas incríveis.
|
|
14
|
-
|
|
15
|
-
Por isso, escolhi a Licença MIT para este projeto. Em termos simples, isso significa que você tem total liberdade para usar, modificar e distribuir este código, até mesmo para fins comerciais, com uma única condição: mantenha o aviso de licença e copyright originais.
|
|
16
|
-
|
|
17
|
-
Agradecemos o seu interesse e contribuição!
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
### MIT License
|
|
1
|
+
MIT License
|
|
22
2
|
|
|
23
3
|
Copyright (c) 2025 Silvio Campos
|
|
24
4
|
|
|
@@ -39,29 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
39
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
40
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
41
21
|
SOFTWARE.
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
### Tradução para Português (Apenas para referência)
|
|
46
|
-
|
|
47
|
-
_Aviso: Esta é uma tradução não oficial e serve apenas para facilitar o entendimento. A versão em inglês é a que possui validade legal._
|
|
48
|
-
|
|
49
|
-
Copyright (c) 2025 Silvio Campos
|
|
50
|
-
|
|
51
|
-
É concedida permissão, gratuitamente, a qualquer pessoa que obtenha uma cópia
|
|
52
|
-
deste software e dos arquivos de documentação associados (o "Software"), para negociar
|
|
53
|
-
o Software sem restrições, incluindo, sem limitação, os direitos
|
|
54
|
-
de usar, copiar, modificar, mesclar, publicar, distribuir, sublicenciar e/ou vender
|
|
55
|
-
cópias do Software, e permitir que as pessoas a quem o Software é
|
|
56
|
-
fornecido o façam, sujeito às seguintes condições:
|
|
57
|
-
|
|
58
|
-
O aviso de copyright acima e este aviso de permissão devem ser incluídos em todas as
|
|
59
|
-
cópias ou partes substanciais do Software.
|
|
60
|
-
|
|
61
|
-
O SOFTWARE É FORNECIDO "COMO ESTÁ", SEM GARANTIA DE QUALQUER TIPO, EXPRESSA OU
|
|
62
|
-
IMPLÍCITA, INCLUINDO, MAS NÃO SE LIMITANDO ÀS GARANTIAS DE COMERCIALIZAÇÃO,
|
|
63
|
-
ADEQUAÇÃO A UM FIM ESPECÍFICO E NÃO VIOLAÇÃO. EM NENHUMA CIRCUNSTÂNCIA OS
|
|
64
|
-
AUTORES OU TITULARES DE DIREITOS AUTORAIS SERÃO RESPONSÁVEIS POR QUALQUER REIVINDICAÇÃO, DANOS OU OUTRA
|
|
65
|
-
RESPONSABILIDADE, SEJA EM UMA AÇÃO DE CONTRATO, ILÍCITO CIVIL OU DE OUTRA FORMA, DECORRENTE DE,
|
|
66
|
-
FORA DE OU EM CONEXÃO COM O SOFTWARE OU O USO OU OUTRAS NEGOCIAÇÕES NO
|
|
67
|
-
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@eusilvio/cep-lookup)
|
|
4
4
|
[](https://www.npmjs.com/package/@eusilvio/cep-lookup)
|
|
5
|
-
[](https://github.com/eusilvio/cep-lookup/actions)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
8
|
A modern, flexible, and agnostic CEP (Brazilian postal code) lookup library written in TypeScript.
|
|
@@ -119,7 +119,7 @@ const cepLookup = new CepLookup({
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
// 2. Look up multiple CEPs
|
|
122
|
-
cepLookup.lookupCeps(cepsToLookup,
|
|
122
|
+
cepLookup.lookupCeps(cepsToLookup, 2).then((results: BulkCepResult[]) => {
|
|
123
123
|
console.log("Bulk lookup results:", results);
|
|
124
124
|
// Output:
|
|
125
125
|
// [
|
|
@@ -166,13 +166,12 @@ Returns a `Promise` that resolves to the address in the default format (`Address
|
|
|
166
166
|
- `cep` (string, **required**): The CEP to be queried.
|
|
167
167
|
- `mapper` ((address: Address) => T, _optional_): A function that receives the default `Address` object and transforms it into a new format `T`.
|
|
168
168
|
|
|
169
|
-
### `cepLookup.lookupCeps(ceps,
|
|
169
|
+
### `cepLookup.lookupCeps(ceps, concurrency?): Promise<BulkCepResult[]>`
|
|
170
170
|
|
|
171
171
|
Looks up multiple CEPs in bulk. Returns a `Promise` that resolves to an array of `BulkCepResult` objects, one for each queried CEP.
|
|
172
172
|
|
|
173
173
|
- `ceps` (string[], **required**): An array of CEP strings to be queried.
|
|
174
|
-
- `
|
|
175
|
-
- `concurrency` (number): The number of parallel requests to make. Defaults to `5`.
|
|
174
|
+
- `concurrency` (number, _optional_): The number of parallel requests to make. Defaults to `5`.
|
|
176
175
|
|
|
177
176
|
> **Note on Deprecated Functions:**
|
|
178
177
|
> Standalone `lookupCep` and `lookupCeps` functions are deprecated and will be removed in a future version. Please use the methods on a `CepLookup` instance instead.
|
|
@@ -249,6 +248,13 @@ const myCustomProvider: Provider = {
|
|
|
249
248
|
npm test
|
|
250
249
|
```
|
|
251
250
|
|
|
251
|
+
## Compatibility and support
|
|
252
|
+
|
|
253
|
+
- Node.js: `20.x`, `22.x`, `24.x`
|
|
254
|
+
- React package: `react >= 16.8`
|
|
255
|
+
- Vue package: `vue ^3`
|
|
256
|
+
- Maintenance policy: [SUPPORT.md](../../SUPPORT.md)
|
|
257
|
+
|
|
252
258
|
## License
|
|
253
259
|
|
|
254
|
-
Distributed under the MIT License.
|
|
260
|
+
Distributed under the MIT License.
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var A=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var z=(s,e)=>{for(var t in e)A(s,t,{get:e[t],enumerable:!0})},B=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of S(e))!I.call(s,i)&&i!==t&&A(s,i,{get:()=>e[i],enumerable:!(r=O(e,i))||r.enumerable});return s};var q=s=>B(A({},"__esModule",{value:!0}),s);var U={};z(U,{AllProvidersFailedError:()=>w,CepLookup:()=>L,CepNotFoundError:()=>T,CepValidationError:()=>v,InMemoryCache:()=>E,ProviderTimeoutError:()=>y,RateLimitError:()=>g});module.exports=q(U);var E=class{constructor(e){this.cache=new Map;this.ttl=e?.ttl??1/0,this.maxSize=e?.maxSize??1/0}get(e){let t=this.cache.get(e);if(t){if(this.ttl!==1/0&&Date.now()-t.timestamp>this.ttl){this.cache.delete(e);return}return t.value}}set(e,t){if(this.cache.has(e)&&this.cache.delete(e),this.cache.size>=this.maxSize){let r=this.cache.keys().next().value;r!==void 0&&this.cache.delete(r)}this.cache.set(e,{value:t,timestamp:Date.now()})}delete(e){this.cache.delete(e)}has(e){if(!this.cache.has(e))return!1;let t=this.cache.get(e);return this.ttl!==1/0&&Date.now()-t.timestamp>this.ttl?(this.cache.delete(e),!1):!0}clear(){this.cache.clear()}};var v=class extends Error{constructor(e){super("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN."),this.name="CepValidationError",this.cep=e}},g=class extends Error{constructor(e,t){super(`Rate limit exceeded: ${e} requests per ${t}ms.`),this.name="RateLimitError",this.limit=e,this.window=t}},y=class extends Error{constructor(e,t){super(`Timeout from ${e}`),this.name="ProviderTimeoutError",this.provider=e,this.timeout=t}},T=class extends Error{constructor(e,t){super("CEP not found"),this.name="CepNotFoundError",this.cep=e,this.provider=t}},w=class extends Error{constructor(e){super("All providers failed to resolve the CEP."),this.name="AllProvidersFailedError",this.errors=e}};var N={AC:"68",AL:"82",AM:"92",AP:"96",BA:"71",CE:"85",DF:"61",ES:"27",GO:"62",MA:"98",MG:"31",MS:"67",MT:"65",PA:"91",PB:"83",PE:"81",PI:"86",PR:"41",RJ:"21",RN:"84",RO:"69",RR:"95",RS:"51",SC:"48",SE:"79",SP:"11",TO:"63"};var x=class{constructor(){this.listeners={}}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].push(t)}off(e,t){let r=this.listeners[e];r&&(this.listeners[e]=r.filter(i=>i!==t))}emit(e,t){let r=this.listeners[e];r&&r.forEach(i=>i(t))}};function F(s){if(!/^(\d{8}|\d{5}-\d{3})$/.test(s))throw new v(s);return s.replace("-","")}function $(s){let e={...s};return Object.keys(e).forEach(t=>{let r=e[t];typeof r=="string"&&(e[t]=r.trim())}),e}function K(s){if(!s.ddd&&s.state){let e=N[s.state];if(e)return{...s,ddd:e}}return s}var L=class{constructor(e){this.requestTimestamps=[];this.providers=e.providers,this.sortedProviders=[...e.providers],this.emitter=new x,this.fetcher=e.fetcher||(async(t,r)=>{let i=await fetch(t,{signal:r});if(!i.ok)throw new Error(`HTTP error! status: ${i.status}`);return i.json()}),this.cache=e.cache,this.rateLimit=e.rateLimit,this.staggerDelay=e.staggerDelay??100,this.retries=e.retries??0,this.retryDelay=e.retryDelay??1e3,this.logger=e.logger}log(e,t){this.logger?.debug(e,t)}on(e,t){this.emitter.on(e,t)}off(e,t){this.emitter.off(e,t)}async warmup(){let e="01001000",t=new AbortController,r=this.providers.map(async n=>{let a=Date.now();try{let c=n.buildUrl(e);return await this.fetcher(c,t.signal),{provider:n,duration:Date.now()-a,error:!1}}catch{return{provider:n,duration:1/0,error:!0}}}),d=(await Promise.all(r)).sort((n,a)=>n.duration-a.duration);return this.sortedProviders=d.map(n=>n.provider).filter(n=>!!n),t.abort(),this.sortedProviders}checkRateLimit(){if(!this.rateLimit)return;let e=Date.now(),t=e-this.rateLimit.per;if(this.requestTimestamps=this.requestTimestamps.filter(r=>r>t),this.requestTimestamps.length>=this.rateLimit.requests)throw new g(this.rateLimit.requests,this.rateLimit.per);this.requestTimestamps.push(e)}async lookup(e,t){this.checkRateLimit();let r=F(e);if(this.log("lookup:start",{cep:r}),this.cache){let n=this.cache.get(r);if(n)return this.log("cache:hit",{cep:r}),this.emitter.emit("cache:hit",{cep:r}),t?t(n):n}let i,d=1+this.retries;for(let n=0;n<d;n++){if(n>0){let a=this.retryDelay*Math.pow(2,n-1);this.log("retry:attempt",{attempt:n,cep:r,delay:a}),await new Promise(c=>setTimeout(c,a))}try{return await this._lookupFromProviders(r,t)}catch(a){if(a instanceof v||a instanceof g)throw a;i=a}}throw i}async _lookupFromProviders(e,t){let r=new AbortController,{signal:i}=r,d=o=>{let m=Date.now(),b=o.buildUrl(e);this.log("provider:start",{provider:o.name,cep:e});let C=new Promise((l,p)=>{if(!o.timeout)return;let f=setTimeout(()=>{i.removeEventListener("abort",R);let M=Date.now()-m,P=new y(o.name,o.timeout);this.log("provider:failure",{provider:o.name,cep:e,error:P.message}),this.emitter.emit("failure",{provider:o.name,cep:e,duration:M,error:P}),p(P)},o.timeout),R=()=>clearTimeout(f);i.addEventListener("abort",R,{once:!0})}),D=this.fetcher(b,i).then(l=>o.transform(l)).then(l=>{let p=Date.now()-m,f=K($(l));return this.log("provider:success",{provider:o.name,cep:e,duration:p}),this.emitter.emit("success",{provider:o.name,cep:e,duration:p,address:f}),this.cache&&this.cache.set(e,f),t?t(f):f}).catch(l=>{let p=Date.now()-m;throw!l.message.includes("Timeout from")&&l.name!=="AbortError"&&(this.log("provider:failure",{provider:o.name,cep:e,error:l.message}),this.emitter.emit("failure",{provider:o.name,cep:e,duration:p,error:l})),l});return Promise.race([D,C])},n=this.sortedProviders[0],a=this.sortedProviders.slice(1);if(a.length===0)try{return await d(n)}finally{r.abort()}let c=null,h=null,u=new Promise((o,m)=>{h=()=>{if(c&&clearTimeout(c),i.aborted)return;let b=a.map(d);Promise.any(b).then(o).catch(m)},c=setTimeout(h,this.staggerDelay)}),k=d(n).catch(o=>{throw h&&h(),o});try{return await Promise.any([k,u])}catch(o){let m=o.errors||[o];throw new w(m)}finally{c&&clearTimeout(c),r.abort()}}async lookupCeps(e,t=5,r){if(!e||e.length===0)return[];let i=new Array(e.length),d=0,n=async()=>{for(;d<e.length;){let c=d++;if(c>=e.length)break;let h=e[c];try{let u=await this.lookup(h);if(u)i[c]={cep:h,data:r?r(u):u,provider:u.service};else throw new Error("No address found")}catch(u){i[c]={cep:h,data:null,error:u}}}},a=Array.from({length:Math.min(t,e.length)},()=>n());return await Promise.all(a),i.filter(Boolean)}};
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var b=class{constructor(e){this.cache=new Map;this.ttl=e?.ttl??1/0,this.maxSize=e?.maxSize??1/0}get(e){let t=this.cache.get(e);if(t){if(this.ttl!==1/0&&Date.now()-t.timestamp>this.ttl){this.cache.delete(e);return}return t.value}}set(e,t){if(this.cache.has(e)&&this.cache.delete(e),this.cache.size>=this.maxSize){let r=this.cache.keys().next().value;r!==void 0&&this.cache.delete(r)}this.cache.set(e,{value:t,timestamp:Date.now()})}delete(e){this.cache.delete(e)}has(e){if(!this.cache.has(e))return!1;let t=this.cache.get(e);return this.ttl!==1/0&&Date.now()-t.timestamp>this.ttl?(this.cache.delete(e),!1):!0}clear(){this.cache.clear()}};var v=class extends Error{constructor(e){super("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN."),this.name="CepValidationError",this.cep=e}},g=class extends Error{constructor(e,t){super(`Rate limit exceeded: ${e} requests per ${t}ms.`),this.name="RateLimitError",this.limit=e,this.window=t}},y=class extends Error{constructor(e,t){super(`Timeout from ${e}`),this.name="ProviderTimeoutError",this.provider=e,this.timeout=t}},P=class extends Error{constructor(e,t){super("CEP not found"),this.name="CepNotFoundError",this.cep=e,this.provider=t}},w=class extends Error{constructor(e){super("All providers failed to resolve the CEP."),this.name="AllProvidersFailedError",this.errors=e}};var L={AC:"68",AL:"82",AM:"92",AP:"96",BA:"71",CE:"85",DF:"61",ES:"27",GO:"62",MA:"98",MG:"31",MS:"67",MT:"65",PA:"91",PB:"83",PE:"81",PI:"86",PR:"41",RJ:"21",RN:"84",RO:"69",RR:"95",RS:"51",SC:"48",SE:"79",SP:"11",TO:"63"};var A=class{constructor(){this.listeners={}}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].push(t)}off(e,t){let r=this.listeners[e];r&&(this.listeners[e]=r.filter(i=>i!==t))}emit(e,t){let r=this.listeners[e];r&&r.forEach(i=>i(t))}};function M(o){if(!/^(\d{8}|\d{5}-\d{3})$/.test(o))throw new v(o);return o.replace("-","")}function O(o){let e={...o};return Object.keys(e).forEach(t=>{let r=e[t];typeof r=="string"&&(e[t]=r.trim())}),e}function S(o){if(!o.ddd&&o.state){let e=L[o.state];if(e)return{...o,ddd:e}}return o}var R=class{constructor(e){this.requestTimestamps=[];this.providers=e.providers,this.sortedProviders=[...e.providers],this.emitter=new A,this.fetcher=e.fetcher||(async(t,r)=>{let i=await fetch(t,{signal:r});if(!i.ok)throw new Error(`HTTP error! status: ${i.status}`);return i.json()}),this.cache=e.cache,this.rateLimit=e.rateLimit,this.staggerDelay=e.staggerDelay??100,this.retries=e.retries??0,this.retryDelay=e.retryDelay??1e3,this.logger=e.logger}log(e,t){this.logger?.debug(e,t)}on(e,t){this.emitter.on(e,t)}off(e,t){this.emitter.off(e,t)}async warmup(){let e="01001000",t=new AbortController,r=this.providers.map(async s=>{let a=Date.now();try{let c=s.buildUrl(e);return await this.fetcher(c,t.signal),{provider:s,duration:Date.now()-a,error:!1}}catch{return{provider:s,duration:1/0,error:!0}}}),d=(await Promise.all(r)).sort((s,a)=>s.duration-a.duration);return this.sortedProviders=d.map(s=>s.provider).filter(s=>!!s),t.abort(),this.sortedProviders}checkRateLimit(){if(!this.rateLimit)return;let e=Date.now(),t=e-this.rateLimit.per;if(this.requestTimestamps=this.requestTimestamps.filter(r=>r>t),this.requestTimestamps.length>=this.rateLimit.requests)throw new g(this.rateLimit.requests,this.rateLimit.per);this.requestTimestamps.push(e)}async lookup(e,t){this.checkRateLimit();let r=M(e);if(this.log("lookup:start",{cep:r}),this.cache){let s=this.cache.get(r);if(s)return this.log("cache:hit",{cep:r}),this.emitter.emit("cache:hit",{cep:r}),t?t(s):s}let i,d=1+this.retries;for(let s=0;s<d;s++){if(s>0){let a=this.retryDelay*Math.pow(2,s-1);this.log("retry:attempt",{attempt:s,cep:r,delay:a}),await new Promise(c=>setTimeout(c,a))}try{return await this._lookupFromProviders(r,t)}catch(a){if(a instanceof v||a instanceof g)throw a;i=a}}throw i}async _lookupFromProviders(e,t){let r=new AbortController,{signal:i}=r,d=n=>{let m=Date.now(),E=n.buildUrl(e);this.log("provider:start",{provider:n.name,cep:e});let k=new Promise((l,p)=>{if(!n.timeout)return;let f=setTimeout(()=>{i.removeEventListener("abort",x);let D=Date.now()-m,T=new y(n.name,n.timeout);this.log("provider:failure",{provider:n.name,cep:e,error:T.message}),this.emitter.emit("failure",{provider:n.name,cep:e,duration:D,error:T}),p(T)},n.timeout),x=()=>clearTimeout(f);i.addEventListener("abort",x,{once:!0})}),C=this.fetcher(E,i).then(l=>n.transform(l)).then(l=>{let p=Date.now()-m,f=S(O(l));return this.log("provider:success",{provider:n.name,cep:e,duration:p}),this.emitter.emit("success",{provider:n.name,cep:e,duration:p,address:f}),this.cache&&this.cache.set(e,f),t?t(f):f}).catch(l=>{let p=Date.now()-m;throw!l.message.includes("Timeout from")&&l.name!=="AbortError"&&(this.log("provider:failure",{provider:n.name,cep:e,error:l.message}),this.emitter.emit("failure",{provider:n.name,cep:e,duration:p,error:l})),l});return Promise.race([C,k])},s=this.sortedProviders[0],a=this.sortedProviders.slice(1);if(a.length===0)try{return await d(s)}finally{r.abort()}let c=null,h=null,u=new Promise((n,m)=>{h=()=>{if(c&&clearTimeout(c),i.aborted)return;let E=a.map(d);Promise.any(E).then(n).catch(m)},c=setTimeout(h,this.staggerDelay)}),N=d(s).catch(n=>{throw h&&h(),n});try{return await Promise.any([N,u])}catch(n){let m=n.errors||[n];throw new w(m)}finally{c&&clearTimeout(c),r.abort()}}async lookupCeps(e,t=5,r){if(!e||e.length===0)return[];let i=new Array(e.length),d=0,s=async()=>{for(;d<e.length;){let c=d++;if(c>=e.length)break;let h=e[c];try{let u=await this.lookup(h);if(u)i[c]={cep:h,data:r?r(u):u,provider:u.service};else throw new Error("No address found")}catch(u){i[c]={cep:h,data:null,error:u}}}},a=Array.from({length:Math.min(t,e.length)},()=>s());return await Promise.all(a),i.filter(Boolean)}};export{w as AllProvidersFailedError,R as CepLookup,P as CepNotFoundError,v as CepValidationError,b as InMemoryCache,y as ProviderTimeoutError,g as RateLimitError};
|
package/dist/providers/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var o=Object.defineProperty;var a=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var f=(r,t)=>{for(var d in t)o(r,d,{get:t[d],enumerable:!0})},P=(r,t,d,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of c(t))!m.call(r,i)&&i!==d&&o(r,i,{get:()=>t[i],enumerable:!(e=a(t,i))||e.enumerable});return r};var l=r=>P(o({},"__esModule",{value:!0}),r);var g={};f(g,{apicepProvider:()=>v,brasilApiProvider:()=>u,openCepProvider:()=>b,viaCepProvider:()=>n});module.exports=l(g);var n={name:"ViaCEP",buildUrl:r=>`https://viacep.com.br/ws/${r}/json/`,transform:r=>{if(!r||r.erro===!0||r.erro==="true")throw new Error("CEP not found");return{cep:(r.cep||"").replace("-",""),state:r.uf||"",city:r.localidade||"",neighborhood:r.bairro||"",street:r.logradouro||"",service:"ViaCEP",ibge:r.ibge||void 0,ddd:r.ddd||void 0}}};var u={name:"BrasilAPI",buildUrl:r=>`https://brasilapi.com.br/api/cep/v1/${r}`,transform:r=>{if(!r||r.errors||r.message)throw new Error(r.message||"CEP not found");return{cep:(r.cep||"").replace("-",""),state:r.state||"",city:r.city||"",neighborhood:r.neighborhood||"",street:r.street||"",service:"BrasilAPI"}}};var v={name:"ApiCEP",buildUrl:r=>`https://cdn.apicep.com/file/apicep/${r}.json`,transform:r=>{if(!r||r.status!==200)throw new Error(r?.message||"CEP not found");return{cep:(r.code||"").replace("-",""),state:r.state||"",city:r.city||"",neighborhood:r.district||"",street:r.address||"",service:"ApiCEP"}}};var b={name:"OpenCEP",buildUrl:r=>`https://opencep.com/v1/${r}`,transform:r=>{if(!r||r.error)throw new Error("CEP not found");return{cep:(r.cep||"").replace("-",""),state:r.uf||"",city:r.localidade||"",neighborhood:r.bairro||"",street:r.logradouro||"",service:"OpenCEP",ibge:r.ibge||void 0}}};
|
package/dist/providers/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var t={name:"ViaCEP",buildUrl:r=>`https://viacep.com.br/ws/${r}/json/`,transform:r=>{if(!r||r.erro===!0||r.erro==="true")throw new Error("CEP not found");return{cep:(r.cep||"").replace("-",""),state:r.uf||"",city:r.localidade||"",neighborhood:r.bairro||"",street:r.logradouro||"",service:"ViaCEP"}}};var
|
|
1
|
+
var t={name:"ViaCEP",buildUrl:r=>`https://viacep.com.br/ws/${r}/json/`,transform:r=>{if(!r||r.erro===!0||r.erro==="true")throw new Error("CEP not found");return{cep:(r.cep||"").replace("-",""),state:r.uf||"",city:r.localidade||"",neighborhood:r.bairro||"",street:r.logradouro||"",service:"ViaCEP",ibge:r.ibge||void 0,ddd:r.ddd||void 0}}};var d={name:"BrasilAPI",buildUrl:r=>`https://brasilapi.com.br/api/cep/v1/${r}`,transform:r=>{if(!r||r.errors||r.message)throw new Error(r.message||"CEP not found");return{cep:(r.cep||"").replace("-",""),state:r.state||"",city:r.city||"",neighborhood:r.neighborhood||"",street:r.street||"",service:"BrasilAPI"}}};var e={name:"ApiCEP",buildUrl:r=>`https://cdn.apicep.com/file/apicep/${r}.json`,transform:r=>{if(!r||r.status!==200)throw new Error(r?.message||"CEP not found");return{cep:(r.code||"").replace("-",""),state:r.state||"",city:r.city||"",neighborhood:r.district||"",street:r.address||"",service:"ApiCEP"}}};var c={name:"OpenCEP",buildUrl:r=>`https://opencep.com/v1/${r}`,transform:r=>{if(!r||r.error)throw new Error("CEP not found");return{cep:(r.cep||"").replace("-",""),state:r.uf||"",city:r.localidade||"",neighborhood:r.bairro||"",street:r.logradouro||"",service:"OpenCEP",ibge:r.ibge||void 0}}};export{e as apicepProvider,d as brasilApiProvider,c as openCepProvider,t as viaCepProvider};
|
|
@@ -7,30 +7,27 @@ export interface Cache {
|
|
|
7
7
|
get(key: string): Address | undefined;
|
|
8
8
|
set(key: string, value: Address): void;
|
|
9
9
|
clear(): void;
|
|
10
|
+
delete?(key: string): void;
|
|
11
|
+
has?(key: string): boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface InMemoryCacheOptions {
|
|
14
|
+
/** Time-to-live in milliseconds. Default: Infinity (no expiry) */
|
|
15
|
+
ttl?: number;
|
|
16
|
+
/** Maximum number of entries. Default: Infinity (no limit) */
|
|
17
|
+
maxSize?: number;
|
|
10
18
|
}
|
|
11
19
|
/**
|
|
12
20
|
* @class InMemoryCache
|
|
13
|
-
* @description
|
|
21
|
+
* @description In-memory cache with optional TTL and size limit.
|
|
14
22
|
*/
|
|
15
23
|
export declare class InMemoryCache implements Cache {
|
|
16
24
|
private cache;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* @param {string} key - The CEP to look up.
|
|
21
|
-
* @returns {Address | undefined} The cached address or undefined if not found.
|
|
22
|
-
*/
|
|
25
|
+
private ttl;
|
|
26
|
+
private maxSize;
|
|
27
|
+
constructor(options?: InMemoryCacheOptions);
|
|
23
28
|
get(key: string): Address | undefined;
|
|
24
|
-
/**
|
|
25
|
-
* @method set
|
|
26
|
-
* @description Stores an address in the cache.
|
|
27
|
-
* @param {string} key - The CEP to use as the cache key.
|
|
28
|
-
* @param {Address} value - The address to store.
|
|
29
|
-
*/
|
|
30
29
|
set(key: string, value: Address): void;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* @description Clears the entire cache.
|
|
34
|
-
*/
|
|
30
|
+
delete(key: string): void;
|
|
31
|
+
has(key: string): boolean;
|
|
35
32
|
clear(): void;
|
|
36
33
|
}
|
package/dist/src/cache/index.js
CHANGED
|
@@ -3,34 +3,49 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.InMemoryCache = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* @class InMemoryCache
|
|
6
|
-
* @description
|
|
6
|
+
* @description In-memory cache with optional TTL and size limit.
|
|
7
7
|
*/
|
|
8
8
|
class InMemoryCache {
|
|
9
|
-
constructor() {
|
|
9
|
+
constructor(options) {
|
|
10
10
|
this.cache = new Map();
|
|
11
|
+
this.ttl = options?.ttl ?? Infinity;
|
|
12
|
+
this.maxSize = options?.maxSize ?? Infinity;
|
|
11
13
|
}
|
|
12
|
-
/**
|
|
13
|
-
* @method get
|
|
14
|
-
* @description Retrieves an address from the cache.
|
|
15
|
-
* @param {string} key - The CEP to look up.
|
|
16
|
-
* @returns {Address | undefined} The cached address or undefined if not found.
|
|
17
|
-
*/
|
|
18
14
|
get(key) {
|
|
19
|
-
|
|
15
|
+
const entry = this.cache.get(key);
|
|
16
|
+
if (!entry)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (this.ttl !== Infinity && Date.now() - entry.timestamp > this.ttl) {
|
|
19
|
+
this.cache.delete(key);
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return entry.value;
|
|
20
23
|
}
|
|
21
|
-
/**
|
|
22
|
-
* @method set
|
|
23
|
-
* @description Stores an address in the cache.
|
|
24
|
-
* @param {string} key - The CEP to use as the cache key.
|
|
25
|
-
* @param {Address} value - The address to store.
|
|
26
|
-
*/
|
|
27
24
|
set(key, value) {
|
|
28
|
-
this.cache.
|
|
25
|
+
if (this.cache.has(key)) {
|
|
26
|
+
this.cache.delete(key);
|
|
27
|
+
}
|
|
28
|
+
if (this.cache.size >= this.maxSize) {
|
|
29
|
+
const oldestKey = this.cache.keys().next().value;
|
|
30
|
+
if (oldestKey !== undefined) {
|
|
31
|
+
this.cache.delete(oldestKey);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
this.cache.set(key, { value, timestamp: Date.now() });
|
|
35
|
+
}
|
|
36
|
+
delete(key) {
|
|
37
|
+
this.cache.delete(key);
|
|
38
|
+
}
|
|
39
|
+
has(key) {
|
|
40
|
+
if (!this.cache.has(key))
|
|
41
|
+
return false;
|
|
42
|
+
const entry = this.cache.get(key);
|
|
43
|
+
if (this.ttl !== Infinity && Date.now() - entry.timestamp > this.ttl) {
|
|
44
|
+
this.cache.delete(key);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
29
48
|
}
|
|
30
|
-
/**
|
|
31
|
-
* @method clear
|
|
32
|
-
* @description Clears the entire cache.
|
|
33
|
-
*/
|
|
34
49
|
clear() {
|
|
35
50
|
this.cache.clear();
|
|
36
51
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dddByState = void 0;
|
|
4
|
+
/** DDD da capital de cada estado (fallback para providers que nao retornam DDD) */
|
|
5
|
+
exports.dddByState = {
|
|
6
|
+
AC: "68", AL: "82", AM: "92", AP: "96", BA: "71",
|
|
7
|
+
CE: "85", DF: "61", ES: "27", GO: "62", MA: "98",
|
|
8
|
+
MG: "31", MS: "67", MT: "65", PA: "91", PB: "83",
|
|
9
|
+
PE: "81", PI: "86", PR: "41", RJ: "21", RN: "84",
|
|
10
|
+
RO: "69", RR: "95", RS: "51", SC: "48", SE: "79",
|
|
11
|
+
SP: "11", TO: "63",
|
|
12
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare class CepValidationError extends Error {
|
|
2
|
+
readonly cep: string;
|
|
3
|
+
constructor(cep: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class RateLimitError extends Error {
|
|
6
|
+
readonly limit: number;
|
|
7
|
+
readonly window: number;
|
|
8
|
+
constructor(limit: number, window: number);
|
|
9
|
+
}
|
|
10
|
+
export declare class ProviderTimeoutError extends Error {
|
|
11
|
+
readonly provider: string;
|
|
12
|
+
readonly timeout: number;
|
|
13
|
+
constructor(provider: string, timeout: number);
|
|
14
|
+
}
|
|
15
|
+
export declare class CepNotFoundError extends Error {
|
|
16
|
+
readonly cep: string;
|
|
17
|
+
readonly provider?: string;
|
|
18
|
+
constructor(cep: string, provider?: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class AllProvidersFailedError extends Error {
|
|
21
|
+
readonly errors: Error[];
|
|
22
|
+
constructor(errors: Error[]);
|
|
23
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AllProvidersFailedError = exports.CepNotFoundError = exports.ProviderTimeoutError = exports.RateLimitError = exports.CepValidationError = void 0;
|
|
4
|
+
class CepValidationError extends Error {
|
|
5
|
+
constructor(cep) {
|
|
6
|
+
super("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN.");
|
|
7
|
+
this.name = "CepValidationError";
|
|
8
|
+
this.cep = cep;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.CepValidationError = CepValidationError;
|
|
12
|
+
class RateLimitError extends Error {
|
|
13
|
+
constructor(limit, window) {
|
|
14
|
+
super(`Rate limit exceeded: ${limit} requests per ${window}ms.`);
|
|
15
|
+
this.name = "RateLimitError";
|
|
16
|
+
this.limit = limit;
|
|
17
|
+
this.window = window;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.RateLimitError = RateLimitError;
|
|
21
|
+
class ProviderTimeoutError extends Error {
|
|
22
|
+
constructor(provider, timeout) {
|
|
23
|
+
super(`Timeout from ${provider}`);
|
|
24
|
+
this.name = "ProviderTimeoutError";
|
|
25
|
+
this.provider = provider;
|
|
26
|
+
this.timeout = timeout;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.ProviderTimeoutError = ProviderTimeoutError;
|
|
30
|
+
class CepNotFoundError extends Error {
|
|
31
|
+
constructor(cep, provider) {
|
|
32
|
+
super("CEP not found");
|
|
33
|
+
this.name = "CepNotFoundError";
|
|
34
|
+
this.cep = cep;
|
|
35
|
+
this.provider = provider;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.CepNotFoundError = CepNotFoundError;
|
|
39
|
+
class AllProvidersFailedError extends Error {
|
|
40
|
+
constructor(errors) {
|
|
41
|
+
super("All providers failed to resolve the CEP.");
|
|
42
|
+
this.name = "AllProvidersFailedError";
|
|
43
|
+
this.errors = errors;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.AllProvidersFailedError = AllProvidersFailedError;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Address, Fetcher, Provider, CepLookupOptions, BulkCepResult, RateLimitOptions, EventName, EventListener, EventMap } from "./types";
|
|
2
|
-
import { Cache, InMemoryCache } from "./cache";
|
|
3
|
-
|
|
2
|
+
import { Cache, InMemoryCache, InMemoryCacheOptions } from "./cache";
|
|
3
|
+
import { CepValidationError, RateLimitError, ProviderTimeoutError, CepNotFoundError, AllProvidersFailedError } from "./errors";
|
|
4
|
+
export type { Address, Fetcher, Provider, CepLookupOptions, BulkCepResult, RateLimitOptions, EventName, EventListener, EventMap, Cache, InMemoryCacheOptions };
|
|
4
5
|
export { InMemoryCache };
|
|
6
|
+
export { CepValidationError, RateLimitError, ProviderTimeoutError, CepNotFoundError, AllProvidersFailedError };
|
|
5
7
|
/**
|
|
6
8
|
* @class CepLookup
|
|
7
9
|
* @description A class for looking up Brazilian postal codes (CEPs) using multiple providers.
|
|
@@ -13,32 +15,24 @@ export declare class CepLookup {
|
|
|
13
15
|
private cache?;
|
|
14
16
|
private rateLimit?;
|
|
15
17
|
private staggerDelay;
|
|
18
|
+
private retries;
|
|
19
|
+
private retryDelay;
|
|
20
|
+
private logger?;
|
|
16
21
|
private requestTimestamps;
|
|
17
22
|
private emitter;
|
|
18
23
|
constructor(options: CepLookupOptions);
|
|
24
|
+
private log;
|
|
19
25
|
on<T extends EventName>(eventName: T, listener: EventListener<T>): void;
|
|
20
26
|
off<T extends EventName>(eventName: T, listener: EventListener<T>): void;
|
|
21
27
|
/**
|
|
22
28
|
* @method warmup
|
|
23
29
|
* @description Pings providers to determine the fastest one and updates the internal priority order.
|
|
24
30
|
* Useful to call on UI events like 'focus' on the CEP input.
|
|
31
|
+
* @returns {Promise<Provider[]>} The list of providers sorted by latency.
|
|
25
32
|
*/
|
|
26
|
-
warmup(): Promise<
|
|
33
|
+
warmup(): Promise<Provider[]>;
|
|
27
34
|
private checkRateLimit;
|
|
28
35
|
lookup<T = Address>(cep: string, mapper?: (address: Address) => T): Promise<T>;
|
|
29
|
-
|
|
36
|
+
private _lookupFromProviders;
|
|
37
|
+
lookupCeps<T = Address>(ceps: string[], concurrency?: number, mapper?: (address: Address) => T): Promise<BulkCepResult<T>[]>;
|
|
30
38
|
}
|
|
31
|
-
/**
|
|
32
|
-
* @deprecated Use `new CepLookup(options).lookup(cep)` instead.
|
|
33
|
-
*/
|
|
34
|
-
export declare function lookupCep<T = Address>(options: CepLookupOptions & {
|
|
35
|
-
cep: string;
|
|
36
|
-
mapper?: (address: Address) => T;
|
|
37
|
-
}): Promise<T>;
|
|
38
|
-
/**
|
|
39
|
-
* @deprecated Use `new CepLookup(options).lookupCeps(ceps)` instead.
|
|
40
|
-
*/
|
|
41
|
-
export declare function lookupCeps(options: CepLookupOptions & {
|
|
42
|
-
ceps: string[];
|
|
43
|
-
concurrency?: number;
|
|
44
|
-
}): Promise<BulkCepResult[]>;
|
package/dist/src/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CepLookup = exports.InMemoryCache = void 0;
|
|
4
|
-
exports.lookupCep = lookupCep;
|
|
5
|
-
exports.lookupCeps = lookupCeps;
|
|
3
|
+
exports.CepLookup = exports.AllProvidersFailedError = exports.CepNotFoundError = exports.ProviderTimeoutError = exports.RateLimitError = exports.CepValidationError = exports.InMemoryCache = void 0;
|
|
6
4
|
const cache_1 = require("./cache");
|
|
7
5
|
Object.defineProperty(exports, "InMemoryCache", { enumerable: true, get: function () { return cache_1.InMemoryCache; } });
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
Object.defineProperty(exports, "CepValidationError", { enumerable: true, get: function () { return errors_1.CepValidationError; } });
|
|
8
|
+
Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_1.RateLimitError; } });
|
|
9
|
+
Object.defineProperty(exports, "ProviderTimeoutError", { enumerable: true, get: function () { return errors_1.ProviderTimeoutError; } });
|
|
10
|
+
Object.defineProperty(exports, "CepNotFoundError", { enumerable: true, get: function () { return errors_1.CepNotFoundError; } });
|
|
11
|
+
Object.defineProperty(exports, "AllProvidersFailedError", { enumerable: true, get: function () { return errors_1.AllProvidersFailedError; } });
|
|
12
|
+
const ddd_by_state_1 = require("./data/ddd-by-state");
|
|
8
13
|
// Minimal EventEmitter for internal use
|
|
9
14
|
class EventEmitter {
|
|
10
15
|
constructor() {
|
|
@@ -41,7 +46,7 @@ class EventEmitter {
|
|
|
41
46
|
function validateCep(cep) {
|
|
42
47
|
const cepRegex = /^(\d{8}|\d{5}-\d{3})$/;
|
|
43
48
|
if (!cepRegex.test(cep)) {
|
|
44
|
-
throw new
|
|
49
|
+
throw new errors_1.CepValidationError(cep);
|
|
45
50
|
}
|
|
46
51
|
return cep.replace("-", "");
|
|
47
52
|
}
|
|
@@ -61,6 +66,19 @@ function sanitizeAddress(address) {
|
|
|
61
66
|
});
|
|
62
67
|
return sanitized;
|
|
63
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* @function enrichAddress
|
|
71
|
+
* @description Enriches an address with DDD fallback when the provider doesn't return it.
|
|
72
|
+
*/
|
|
73
|
+
function enrichAddress(address) {
|
|
74
|
+
if (!address.ddd && address.state) {
|
|
75
|
+
const fallbackDdd = ddd_by_state_1.dddByState[address.state];
|
|
76
|
+
if (fallbackDdd) {
|
|
77
|
+
return { ...address, ddd: fallbackDdd };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return address;
|
|
81
|
+
}
|
|
64
82
|
/**
|
|
65
83
|
* @class CepLookup
|
|
66
84
|
* @description A class for looking up Brazilian postal codes (CEPs) using multiple providers.
|
|
@@ -81,6 +99,12 @@ class CepLookup {
|
|
|
81
99
|
this.cache = options.cache;
|
|
82
100
|
this.rateLimit = options.rateLimit;
|
|
83
101
|
this.staggerDelay = options.staggerDelay ?? 100;
|
|
102
|
+
this.retries = options.retries ?? 0;
|
|
103
|
+
this.retryDelay = options.retryDelay ?? 1000;
|
|
104
|
+
this.logger = options.logger;
|
|
105
|
+
}
|
|
106
|
+
log(msg, data) {
|
|
107
|
+
this.logger?.debug(msg, data);
|
|
84
108
|
}
|
|
85
109
|
on(eventName, listener) {
|
|
86
110
|
this.emitter.on(eventName, listener);
|
|
@@ -92,6 +116,7 @@ class CepLookup {
|
|
|
92
116
|
* @method warmup
|
|
93
117
|
* @description Pings providers to determine the fastest one and updates the internal priority order.
|
|
94
118
|
* Useful to call on UI events like 'focus' on the CEP input.
|
|
119
|
+
* @returns {Promise<Provider[]>} The list of providers sorted by latency.
|
|
95
120
|
*/
|
|
96
121
|
async warmup() {
|
|
97
122
|
const controlCep = "01001000"; // Praça da Sé (Fixed Valid CEP)
|
|
@@ -117,6 +142,7 @@ class CepLookup {
|
|
|
117
142
|
.filter(p => !!p);
|
|
118
143
|
// Abort any lingering requests (though we awaited all)
|
|
119
144
|
controller.abort();
|
|
145
|
+
return this.sortedProviders;
|
|
120
146
|
}
|
|
121
147
|
checkRateLimit() {
|
|
122
148
|
if (!this.rateLimit)
|
|
@@ -125,33 +151,57 @@ class CepLookup {
|
|
|
125
151
|
const windowStart = now - this.rateLimit.per;
|
|
126
152
|
this.requestTimestamps = this.requestTimestamps.filter((ts) => ts > windowStart);
|
|
127
153
|
if (this.requestTimestamps.length >= this.rateLimit.requests) {
|
|
128
|
-
throw new
|
|
154
|
+
throw new errors_1.RateLimitError(this.rateLimit.requests, this.rateLimit.per);
|
|
129
155
|
}
|
|
130
156
|
this.requestTimestamps.push(now);
|
|
131
157
|
}
|
|
132
158
|
async lookup(cep, mapper) {
|
|
133
159
|
this.checkRateLimit();
|
|
134
160
|
const cleanedCep = validateCep(cep);
|
|
161
|
+
this.log('lookup:start', { cep: cleanedCep });
|
|
135
162
|
if (this.cache) {
|
|
136
163
|
const cachedAddress = this.cache.get(cleanedCep);
|
|
137
164
|
if (cachedAddress) {
|
|
165
|
+
this.log('cache:hit', { cep: cleanedCep });
|
|
138
166
|
this.emitter.emit('cache:hit', { cep: cleanedCep });
|
|
139
167
|
return mapper ? mapper(cachedAddress) : cachedAddress;
|
|
140
168
|
}
|
|
141
169
|
}
|
|
170
|
+
let lastError;
|
|
171
|
+
const maxAttempts = 1 + this.retries;
|
|
172
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
173
|
+
if (attempt > 0) {
|
|
174
|
+
const delay = this.retryDelay * Math.pow(2, attempt - 1);
|
|
175
|
+
this.log('retry:attempt', { attempt, cep: cleanedCep, delay });
|
|
176
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
return await this._lookupFromProviders(cleanedCep, mapper);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
if (error instanceof errors_1.CepValidationError || error instanceof errors_1.RateLimitError) {
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
lastError = error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
throw lastError;
|
|
189
|
+
}
|
|
190
|
+
async _lookupFromProviders(cleanedCep, mapper) {
|
|
142
191
|
const controller = new AbortController();
|
|
143
192
|
const { signal } = controller;
|
|
144
|
-
// Helper to create the promise for a specific provider
|
|
145
193
|
const createProviderPromise = (provider) => {
|
|
146
194
|
const startTime = Date.now();
|
|
147
195
|
const url = provider.buildUrl(cleanedCep);
|
|
196
|
+
this.log('provider:start', { provider: provider.name, cep: cleanedCep });
|
|
148
197
|
const timeoutPromise = new Promise((_, reject) => {
|
|
149
198
|
if (!provider.timeout)
|
|
150
199
|
return;
|
|
151
200
|
const timeoutId = setTimeout(() => {
|
|
152
201
|
signal.removeEventListener('abort', onAbort);
|
|
153
202
|
const duration = Date.now() - startTime;
|
|
154
|
-
const error = new
|
|
203
|
+
const error = new errors_1.ProviderTimeoutError(provider.name, provider.timeout);
|
|
204
|
+
this.log('provider:failure', { provider: provider.name, cep: cleanedCep, error: error.message });
|
|
155
205
|
this.emitter.emit('failure', { provider: provider.name, cep: cleanedCep, duration, error });
|
|
156
206
|
reject(error);
|
|
157
207
|
}, provider.timeout);
|
|
@@ -162,7 +212,8 @@ class CepLookup {
|
|
|
162
212
|
.then((response) => provider.transform(response))
|
|
163
213
|
.then((address) => {
|
|
164
214
|
const duration = Date.now() - startTime;
|
|
165
|
-
const sanitizedAddress = sanitizeAddress(address);
|
|
215
|
+
const sanitizedAddress = enrichAddress(sanitizeAddress(address));
|
|
216
|
+
this.log('provider:success', { provider: provider.name, cep: cleanedCep, duration });
|
|
166
217
|
this.emitter.emit('success', { provider: provider.name, cep: cleanedCep, duration, address: sanitizedAddress });
|
|
167
218
|
if (this.cache) {
|
|
168
219
|
this.cache.set(cleanedCep, sanitizedAddress);
|
|
@@ -171,18 +222,16 @@ class CepLookup {
|
|
|
171
222
|
})
|
|
172
223
|
.catch((error) => {
|
|
173
224
|
const duration = Date.now() - startTime;
|
|
174
|
-
// Only emit failure if it's not a self-induced abortion or timeout handled elsewhere
|
|
175
225
|
if (!error.message.includes('Timeout from') && error.name !== 'AbortError') {
|
|
226
|
+
this.log('provider:failure', { provider: provider.name, cep: cleanedCep, error: error.message });
|
|
176
227
|
this.emitter.emit('failure', { provider: provider.name, cep: cleanedCep, duration, error });
|
|
177
228
|
}
|
|
178
229
|
throw error;
|
|
179
230
|
});
|
|
180
231
|
return Promise.race([fetchPromise, timeoutPromise]);
|
|
181
232
|
};
|
|
182
|
-
// Staggered Strategy using sortedProviders
|
|
183
233
|
const bestProvider = this.sortedProviders[0];
|
|
184
234
|
const otherProviders = this.sortedProviders.slice(1);
|
|
185
|
-
// If we only have one provider, just execute it
|
|
186
235
|
if (otherProviders.length === 0) {
|
|
187
236
|
try {
|
|
188
237
|
return await createProviderPromise(bestProvider);
|
|
@@ -191,7 +240,6 @@ class CepLookup {
|
|
|
191
240
|
controller.abort();
|
|
192
241
|
}
|
|
193
242
|
}
|
|
194
|
-
// Execute primary and manage staggering
|
|
195
243
|
let staggerTimeout = null;
|
|
196
244
|
let triggerOthers = null;
|
|
197
245
|
const secondaryPromise = new Promise((resolve, reject) => {
|
|
@@ -206,7 +254,6 @@ class CepLookup {
|
|
|
206
254
|
staggerTimeout = setTimeout(triggerOthers, this.staggerDelay);
|
|
207
255
|
});
|
|
208
256
|
const primaryPromise = createProviderPromise(bestProvider).catch((err) => {
|
|
209
|
-
// If primary fails, trigger others immediately
|
|
210
257
|
if (triggerOthers)
|
|
211
258
|
triggerOthers();
|
|
212
259
|
throw err;
|
|
@@ -214,13 +261,17 @@ class CepLookup {
|
|
|
214
261
|
try {
|
|
215
262
|
return await Promise.any([primaryPromise, secondaryPromise]);
|
|
216
263
|
}
|
|
264
|
+
catch (aggregateError) {
|
|
265
|
+
const errors = aggregateError.errors || [aggregateError];
|
|
266
|
+
throw new errors_1.AllProvidersFailedError(errors);
|
|
267
|
+
}
|
|
217
268
|
finally {
|
|
218
269
|
if (staggerTimeout)
|
|
219
270
|
clearTimeout(staggerTimeout);
|
|
220
271
|
controller.abort();
|
|
221
272
|
}
|
|
222
273
|
}
|
|
223
|
-
async lookupCeps(ceps, concurrency = 5) {
|
|
274
|
+
async lookupCeps(ceps, concurrency = 5, mapper) {
|
|
224
275
|
if (!ceps || ceps.length === 0) {
|
|
225
276
|
return [];
|
|
226
277
|
}
|
|
@@ -235,7 +286,11 @@ class CepLookup {
|
|
|
235
286
|
try {
|
|
236
287
|
const address = await this.lookup(cep);
|
|
237
288
|
if (address) {
|
|
238
|
-
results[currentIndex] = {
|
|
289
|
+
results[currentIndex] = {
|
|
290
|
+
cep,
|
|
291
|
+
data: mapper ? mapper(address) : address,
|
|
292
|
+
provider: address.service,
|
|
293
|
+
};
|
|
239
294
|
}
|
|
240
295
|
else {
|
|
241
296
|
throw new Error('No address found');
|
|
@@ -252,21 +307,3 @@ class CepLookup {
|
|
|
252
307
|
}
|
|
253
308
|
}
|
|
254
309
|
exports.CepLookup = CepLookup;
|
|
255
|
-
/**
|
|
256
|
-
* @deprecated Use `new CepLookup(options).lookup(cep)` instead.
|
|
257
|
-
*/
|
|
258
|
-
function lookupCep(options) {
|
|
259
|
-
console.warn("[cep-lookup] The standalone `lookupCep` function is deprecated and will be removed in a future version. Please use `new CepLookup(options).lookup(cep)` instead.");
|
|
260
|
-
const { cep, providers, fetcher, mapper, cache, rateLimit } = options;
|
|
261
|
-
const cepLookup = new CepLookup({ providers, fetcher, cache, rateLimit });
|
|
262
|
-
return cepLookup.lookup(cep, mapper);
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* @deprecated Use `new CepLookup(options).lookupCeps(ceps)` instead.
|
|
266
|
-
*/
|
|
267
|
-
async function lookupCeps(options) {
|
|
268
|
-
console.warn("[cep-lookup] The standalone `lookupCeps` function is deprecated and will be removed in a future version. Please use `new CepLookup(options).lookupCeps(ceps)` instead.");
|
|
269
|
-
const { ceps, providers, fetcher, cache, concurrency = 5, rateLimit } = options;
|
|
270
|
-
const cepLookup = new CepLookup({ providers, fetcher, cache, rateLimit });
|
|
271
|
-
return cepLookup.lookupCeps(ceps, concurrency);
|
|
272
|
-
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Provider } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* @const {Provider} openCepProvider
|
|
4
|
+
* @description Provider for the OpenCEP service.
|
|
5
|
+
* @property {string} name - "OpenCEP".
|
|
6
|
+
* @property {(cep: string) => string} buildUrl - Constructs the URL for OpenCEP API.
|
|
7
|
+
* @property {(response: any) => Address} transform - Transforms OpenCEP's response into a standardized `Address` object.
|
|
8
|
+
* @throws {Error} If OpenCEP response indicates an error.
|
|
9
|
+
*/
|
|
10
|
+
export declare const openCepProvider: Provider;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openCepProvider = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* @const {Provider} openCepProvider
|
|
6
|
+
* @description Provider for the OpenCEP service.
|
|
7
|
+
* @property {string} name - "OpenCEP".
|
|
8
|
+
* @property {(cep: string) => string} buildUrl - Constructs the URL for OpenCEP API.
|
|
9
|
+
* @property {(response: any) => Address} transform - Transforms OpenCEP's response into a standardized `Address` object.
|
|
10
|
+
* @throws {Error} If OpenCEP response indicates an error.
|
|
11
|
+
*/
|
|
12
|
+
exports.openCepProvider = {
|
|
13
|
+
name: "OpenCEP",
|
|
14
|
+
buildUrl: (cep) => `https://opencep.com/v1/${cep}`,
|
|
15
|
+
transform: (response) => {
|
|
16
|
+
if (!response || response.error) {
|
|
17
|
+
throw new Error("CEP not found");
|
|
18
|
+
}
|
|
19
|
+
// OpenCEP returns status code 404 for not found, which fetcher catches.
|
|
20
|
+
// But sometimes APIs return 200 with error data.
|
|
21
|
+
// Assuming standard JSON return based on description.
|
|
22
|
+
return {
|
|
23
|
+
cep: (response.cep || "").replace("-", ""),
|
|
24
|
+
state: response.uf || "",
|
|
25
|
+
city: response.localidade || "",
|
|
26
|
+
neighborhood: response.bairro || "",
|
|
27
|
+
street: response.logradouro || "",
|
|
28
|
+
service: "OpenCEP",
|
|
29
|
+
ibge: response.ibge || undefined,
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
package/dist/src/types.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface Address {
|
|
|
10
10
|
neighborhood: string;
|
|
11
11
|
street: string;
|
|
12
12
|
service: string;
|
|
13
|
+
ibge?: string;
|
|
14
|
+
ddd?: string;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* @interface Provider
|
|
@@ -44,14 +46,22 @@ export interface CepLookupOptions {
|
|
|
44
46
|
cache?: Cache;
|
|
45
47
|
rateLimit?: RateLimitOptions;
|
|
46
48
|
staggerDelay?: number;
|
|
49
|
+
/** Number of retries after all providers fail. Default: 0 */
|
|
50
|
+
retries?: number;
|
|
51
|
+
/** Base delay in ms between retries (exponential backoff). Default: 1000 */
|
|
52
|
+
retryDelay?: number;
|
|
53
|
+
/** Optional logger for debug output */
|
|
54
|
+
logger?: {
|
|
55
|
+
debug: (msg: string, data?: Record<string, unknown>) => void;
|
|
56
|
+
};
|
|
47
57
|
}
|
|
48
58
|
/**
|
|
49
59
|
* @interface BulkCepResult
|
|
50
60
|
* @description Represents the result for a single CEP in a bulk lookup operation.
|
|
51
61
|
*/
|
|
52
|
-
export interface BulkCepResult {
|
|
62
|
+
export interface BulkCepResult<T = Address> {
|
|
53
63
|
cep: string;
|
|
54
|
-
data:
|
|
64
|
+
data: T | null;
|
|
55
65
|
provider?: string;
|
|
56
66
|
error?: Error;
|
|
57
67
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eusilvio/cep-lookup",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "A agnostic, performant and flexible CEP lookup library with race strategy and caching.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Silvio Souza",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"scripts": {
|
|
35
35
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
36
36
|
"build": "npm run clean && tsc --emitDeclarationOnly --outDir dist && esbuild src/index.ts src/providers/index.ts --bundle --platform=neutral --format=cjs --outdir=dist --out-extension:.js=.cjs --minify && esbuild src/index.ts src/providers/index.ts --bundle --platform=neutral --format=esm --outdir=dist --out-extension:.js=.mjs --minify",
|
|
37
|
-
"test": "jest"
|
|
37
|
+
"test": "jest",
|
|
38
|
+
"test:html": "npx http-server . -o examples/index.html -c-1"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@types/jest": "^30.0.0",
|