@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 CHANGED
@@ -1,24 +1,4 @@
1
- # Project License
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
  [![NPM Version](https://img.shields.io/npm/v/@eusilvio/cep-lookup.svg)](https://www.npmjs.com/package/@eusilvio/cep-lookup)
4
4
  [![NPM Unpacked Size](https://img.shields.io/npm/unpacked-size/@eusilvio/cep-lookup)](https://www.npmjs.com/package/@eusilvio/cep-lookup)
5
- [![Build Status](https://img.shields.io/github/workflow/status/eusilvio/cep-lookup/CI)](https://github.com/eusilvio/cep-lookup/actions)
5
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/eusilvio/cep-lookup/ci.yml)](https://github.com/eusilvio/cep-lookup/actions)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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, { concurrency: 2 }).then((results: BulkCepResult[]) => {
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, options?): Promise<BulkCepResult[]>`
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
- - `options` (object, _optional_): An options object.
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 g=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var D=(i,e)=>{for(var t in e)g(i,t,{get:e[t],enumerable:!0})},O=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of x(e))!R.call(i,s)&&s!==t&&g(i,s,{get:()=>e[s],enumerable:!(r=N(e,s))||r.enumerable});return i};var q=i=>O(g({},"__esModule",{value:!0}),i);var z={};D(z,{CepLookup:()=>f,InMemoryCache:()=>w,lookupCep:()=>M,lookupCeps:()=>$});module.exports=q(z);var w=class{constructor(){this.cache=new Map}get(e){return this.cache.get(e)}set(e,t){this.cache.set(e,t)}clear(){this.cache.clear()}};var k=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(s=>s!==t))}emit(e,t){let r=this.listeners[e];r&&r.forEach(s=>s(t))}};function B(i){if(!/^(\d{8}|\d{5}-\d{3})$/.test(i))throw new Error("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN.");return i.replace("-","")}function I(i){let e={...i};return Object.keys(e).forEach(t=>{let r=e[t];typeof r=="string"&&(e[t]=r.trim())}),e}var f=class{constructor(e){this.requestTimestamps=[];this.providers=e.providers,this.sortedProviders=[...e.providers],this.emitter=new k,this.fetcher=e.fetcher||(async(t,r)=>{let s=await fetch(t,{signal:r});if(!s.ok)throw new Error(`HTTP error! status: ${s.status}`);return s.json()}),this.cache=e.cache,this.rateLimit=e.rateLimit,this.staggerDelay=e.staggerDelay??100}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 o=>{let a=Date.now();try{let u=o.buildUrl(e);return await this.fetcher(u,t.signal),{provider:o,duration:Date.now()-a,error:!1}}catch{return{provider:o,duration:1/0,error:!0}}}),c=(await Promise.all(r)).sort((o,a)=>o.duration-a.duration);this.sortedProviders=c.map(o=>o.provider).filter(o=>!!o),t.abort()}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 Error(`Rate limit exceeded: ${this.rateLimit.requests} requests per ${this.rateLimit.per}ms.`);this.requestTimestamps.push(e)}async lookup(e,t){this.checkRateLimit();let r=B(e);if(this.cache){let n=this.cache.get(r);if(n)return this.emitter.emit("cache:hit",{cep:r}),t?t(n):n}let s=new AbortController,{signal:c}=s,o=n=>{let h=Date.now(),T=n.buildUrl(r),b=new Promise((l,m)=>{if(!n.timeout)return;let p=setTimeout(()=>{c.removeEventListener("abort",L);let A=Date.now()-h,P=new Error(`Timeout from ${n.name}`);this.emitter.emit("failure",{provider:n.name,cep:r,duration:A,error:P}),m(P)},n.timeout),L=()=>clearTimeout(p);c.addEventListener("abort",L,{once:!0})}),C=this.fetcher(T,c).then(l=>n.transform(l)).then(l=>{let m=Date.now()-h,p=I(l);return this.emitter.emit("success",{provider:n.name,cep:r,duration:m,address:p}),this.cache&&this.cache.set(r,p),t?t(p):p}).catch(l=>{let m=Date.now()-h;throw!l.message.includes("Timeout from")&&l.name!=="AbortError"&&this.emitter.emit("failure",{provider:n.name,cep:r,duration:m,error:l}),l});return Promise.race([C,b])},a=this.sortedProviders[0],u=this.sortedProviders.slice(1);if(u.length===0)try{return await o(a)}finally{s.abort()}let d=null,v=null,y=new Promise((n,h)=>{v=()=>{if(d&&clearTimeout(d),c.aborted)return;let T=u.map(o);Promise.any(T).then(n).catch(h)},d=setTimeout(v,this.staggerDelay)}),E=o(a).catch(n=>{throw v&&v(),n});try{return await Promise.any([E,y])}finally{d&&clearTimeout(d),s.abort()}}async lookupCeps(e,t=5){if(!e||e.length===0)return[];let r=new Array(e.length),s=0,c=async()=>{for(;s<e.length;){let a=s++;if(a>=e.length)break;let u=e[a];try{let d=await this.lookup(u);if(d)r[a]={cep:u,data:d,provider:d.service};else throw new Error("No address found")}catch(d){r[a]={cep:u,data:null,error:d}}}},o=Array.from({length:Math.min(t,e.length)},()=>c());return await Promise.all(o),r.filter(Boolean)}};function M(i){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.");let{cep:e,providers:t,fetcher:r,mapper:s,cache:c,rateLimit:o}=i;return new f({providers:t,fetcher:r,cache:c,rateLimit:o}).lookup(e,s)}async function $(i){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.");let{ceps:e,providers:t,fetcher:r,cache:s,concurrency:c=5,rateLimit:o}=i;return new f({providers:t,fetcher:r,cache:s,rateLimit:o}).lookupCeps(e,c)}
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 T=class{constructor(){this.cache=new Map}get(e){return this.cache.get(e)}set(e,t){this.cache.set(e,t)}clear(){this.cache.clear()}};var g=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(s=>s!==t))}emit(e,t){let r=this.listeners[e];r&&r.forEach(s=>s(t))}};function A(c){if(!/^(\d{8}|\d{5}-\d{3})$/.test(c))throw new Error("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN.");return c.replace("-","")}function N(c){let e={...c};return Object.keys(e).forEach(t=>{let r=e[t];typeof r=="string"&&(e[t]=r.trim())}),e}var v=class{constructor(e){this.requestTimestamps=[];this.providers=e.providers,this.sortedProviders=[...e.providers],this.emitter=new g,this.fetcher=e.fetcher||(async(t,r)=>{let s=await fetch(t,{signal:r});if(!s.ok)throw new Error(`HTTP error! status: ${s.status}`);return s.json()}),this.cache=e.cache,this.rateLimit=e.rateLimit,this.staggerDelay=e.staggerDelay??100}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 i=>{let n=Date.now();try{let u=i.buildUrl(e);return await this.fetcher(u,t.signal),{provider:i,duration:Date.now()-n,error:!1}}catch{return{provider:i,duration:1/0,error:!0}}}),a=(await Promise.all(r)).sort((i,n)=>i.duration-n.duration);this.sortedProviders=a.map(i=>i.provider).filter(i=>!!i),t.abort()}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 Error(`Rate limit exceeded: ${this.rateLimit.requests} requests per ${this.rateLimit.per}ms.`);this.requestTimestamps.push(e)}async lookup(e,t){this.checkRateLimit();let r=A(e);if(this.cache){let o=this.cache.get(r);if(o)return this.emitter.emit("cache:hit",{cep:r}),t?t(o):o}let s=new AbortController,{signal:a}=s,i=o=>{let h=Date.now(),w=o.buildUrl(r),E=new Promise((l,m)=>{if(!o.timeout)return;let p=setTimeout(()=>{a.removeEventListener("abort",k);let C=Date.now()-h,L=new Error(`Timeout from ${o.name}`);this.emitter.emit("failure",{provider:o.name,cep:r,duration:C,error:L}),m(L)},o.timeout),k=()=>clearTimeout(p);a.addEventListener("abort",k,{once:!0})}),b=this.fetcher(w,a).then(l=>o.transform(l)).then(l=>{let m=Date.now()-h,p=N(l);return this.emitter.emit("success",{provider:o.name,cep:r,duration:m,address:p}),this.cache&&this.cache.set(r,p),t?t(p):p}).catch(l=>{let m=Date.now()-h;throw!l.message.includes("Timeout from")&&l.name!=="AbortError"&&this.emitter.emit("failure",{provider:o.name,cep:r,duration:m,error:l}),l});return Promise.race([b,E])},n=this.sortedProviders[0],u=this.sortedProviders.slice(1);if(u.length===0)try{return await i(n)}finally{s.abort()}let d=null,f=null,P=new Promise((o,h)=>{f=()=>{if(d&&clearTimeout(d),a.aborted)return;let w=u.map(i);Promise.any(w).then(o).catch(h)},d=setTimeout(f,this.staggerDelay)}),y=i(n).catch(o=>{throw f&&f(),o});try{return await Promise.any([y,P])}finally{d&&clearTimeout(d),s.abort()}}async lookupCeps(e,t=5){if(!e||e.length===0)return[];let r=new Array(e.length),s=0,a=async()=>{for(;s<e.length;){let n=s++;if(n>=e.length)break;let u=e[n];try{let d=await this.lookup(u);if(d)r[n]={cep:u,data:d,provider:d.service};else throw new Error("No address found")}catch(d){r[n]={cep:u,data:null,error:d}}}},i=Array.from({length:Math.min(t,e.length)},()=>a());return await Promise.all(i),r.filter(Boolean)}};function O(c){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.");let{cep:e,providers:t,fetcher:r,mapper:s,cache:a,rateLimit:i}=c;return new v({providers:t,fetcher:r,cache:a,rateLimit:i}).lookup(e,s)}async function q(c){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.");let{ceps:e,providers:t,fetcher:r,cache:s,concurrency:a=5,rateLimit:i}=c;return new v({providers:t,fetcher:r,cache:s,rateLimit:i}).lookupCeps(e,a)}export{v as CepLookup,T as InMemoryCache,O as lookupCep,q as lookupCeps};
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};
@@ -1 +1 @@
1
- "use strict";var e=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var P=(r,t)=>{for(var o in t)e(r,o,{get:t[o],enumerable:!0})},f=(r,t,o,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of c(t))!m.call(r,i)&&i!==o&&e(r,i,{get:()=>t[i],enumerable:!(a=d(t,i))||a.enumerable});return r};var l=r=>f(e({},"__esModule",{value:!0}),r);var b={};P(b,{apicepProvider:()=>u,brasilApiProvider:()=>h,viaCepProvider:()=>v});module.exports=l(b);var v={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 h={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 u={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"}}};
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}}};
@@ -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 o={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 a={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"}}};export{a as apicepProvider,o as brasilApiProvider,t as viaCepProvider};
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 A simple in-memory cache implementation for storing CEP lookups.
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
- * @method get
19
- * @description Retrieves an address from the cache.
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
- * @method clear
33
- * @description Clears the entire cache.
34
- */
30
+ delete(key: string): void;
31
+ has(key: string): boolean;
35
32
  clear(): void;
36
33
  }
@@ -3,34 +3,49 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InMemoryCache = void 0;
4
4
  /**
5
5
  * @class InMemoryCache
6
- * @description A simple in-memory cache implementation for storing CEP lookups.
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
- return this.cache.get(key);
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.set(key, value);
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,2 @@
1
+ /** DDD da capital de cada estado (fallback para providers que nao retornam DDD) */
2
+ export declare const dddByState: Record<string, string>;
@@ -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;
@@ -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
- export type { Address, Fetcher, Provider, CepLookupOptions, BulkCepResult, RateLimitOptions, EventName, EventListener, EventMap, Cache };
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<void>;
33
+ warmup(): Promise<Provider[]>;
27
34
  private checkRateLimit;
28
35
  lookup<T = Address>(cep: string, mapper?: (address: Address) => T): Promise<T>;
29
- lookupCeps(ceps: string[], concurrency?: number): Promise<BulkCepResult[]>;
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 Error("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN.");
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 Error(`Rate limit exceeded: ${this.rateLimit.requests} requests per ${this.rateLimit.per}ms.`);
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 Error(`Timeout from ${provider.name}`);
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] = { cep, data: address, provider: address.service };
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
- }
@@ -1,3 +1,4 @@
1
1
  export * from "./viacep";
2
2
  export * from "./brasil-api";
3
3
  export * from "./apicep";
4
+ export * from "./opencep";
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./viacep"), exports);
18
18
  __exportStar(require("./brasil-api"), exports);
19
19
  __exportStar(require("./apicep"), exports);
20
+ __exportStar(require("./opencep"), exports);
@@ -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
+ };
@@ -23,6 +23,8 @@ exports.viaCepProvider = {
23
23
  neighborhood: response.bairro || "",
24
24
  street: response.logradouro || "",
25
25
  service: "ViaCEP",
26
+ ibge: response.ibge || undefined,
27
+ ddd: response.ddd || undefined,
26
28
  };
27
29
  },
28
30
  };
@@ -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: Address | null;
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.4.0",
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",