@eusilvio/cep-lookup 2.4.0 → 2.6.0

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
@@ -1,28 +1,10 @@
1
1
  # @eusilvio/cep-lookup
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
- [![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)
4
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/eusilvio/cep-lookup/ci.yml)](https://github.com/eusilvio/cep-lookup/actions)
6
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
6
 
8
- A modern, flexible, and agnostic CEP (Brazilian postal code) lookup library written in TypeScript.
9
-
10
- ## About
11
-
12
- `@eusilvio/cep-lookup` was created to solve address lookup from a CEP in a different way. Instead of relying on a single data source, it queries multiple services simultaneously and returns the response from the fastest one.
13
-
14
- Its agnostic design allows it to be used in any JavaScript environment with any HTTP client, and its powerful "mapper" system allows you to format the data output exactly as you need.
15
-
16
- ## Key Features
17
-
18
- - **Multiple Providers (Race Strategy)**: Queries multiple CEP APIs at the same time and uses the first valid response.
19
- - **Class-Based API**: Create a reusable instance with your settings.
20
- - **Bulk Lookups**: Efficiently look up multiple CEPs with a single method call.
21
- - **Customizable Return Format**: Provide a `mapper` function to transform the address data into any format your application needs.
22
- - **HTTP Client Agnostic**: You provide the fetch function, giving you full control over the requests. Defaults to global `fetch` if not provided.
23
- - **Modular and Extensible Architecture**: Adding a new CEP data source is trivial.
24
- - **Fully Typed**: Developed with TypeScript to ensure type safety and a great developer experience.
25
- - **Caching**: Built-in support for caching to avoid repeated requests for the same CEP.
7
+ Core CEP lookup engine with multi-provider race, resilience controls, and metrics.
26
8
 
27
9
  ## Installation
28
10
 
@@ -30,225 +12,142 @@ Its agnostic design allows it to be used in any JavaScript environment with any
30
12
  npm install @eusilvio/cep-lookup
31
13
  ```
32
14
 
33
- ## How to Use
15
+ ## Features
34
16
 
35
- `@eusilvio/cep-lookup` is designed to be straightforward. You create a reusable instance of the `CepLookup` class with your desired settings and use its methods to look up single or multiple CEPs. The library also includes a simple in-memory cache to avoid repeated requests, which you can use or replace with your own implementation.
17
+ - Multi-provider race strategy.
18
+ - Cache and rate limiting.
19
+ - Retry with exponential backoff.
20
+ - Standardized errors with error codes.
21
+ - Circuit breaker per provider.
22
+ - Provider health score and runtime metrics.
23
+ - Event-based observability.
36
24
 
37
- ### Example 1: Basic Usage
25
+ ## Basic Usage
38
26
 
39
- ```typescript
40
- import { CepLookup, Address } from "@eusilvio/cep-lookup";
41
- import {
42
- viaCepProvider,
43
- brasilApiProvider,
44
- } from "@eusilvio/cep-lookup/providers";
27
+ ```ts
28
+ import { CepLookup } from "@eusilvio/cep-lookup";
29
+ import { viaCepProvider, brasilApiProvider } from "@eusilvio/cep-lookup/providers";
45
30
 
46
- // 1. Create an instance of CepLookup
47
- const cepLookup = new CepLookup({
31
+ const lookup = new CepLookup({
48
32
  providers: [viaCepProvider, brasilApiProvider],
49
33
  });
50
34
 
51
- // 2. Look up a CEP
52
- cepLookup.lookup("01001-000").then((address: Address) => {
53
- console.log("Address found:", address);
54
- // Output:
55
- // {
56
- // cep: '01001-000',
57
- // state: 'SP',
58
- // city: 'São Paulo',
59
- // neighborhood: 'Sé',
60
- // street: 'Praça da Sé',
61
- // service: 'ViaCEP'
62
- // }
63
- });
35
+ const address = await lookup.lookup("01001-000");
36
+ console.log(address);
64
37
  ```
65
38
 
66
- ### Example 2: Custom Return with `mapper`
67
-
68
- ```typescript
69
- import { CepLookup, Address } from "@eusilvio/cep-lookup";
70
- import { viaCepProvider } from "@eusilvio/cep-lookup/providers";
39
+ ## Error Handling
71
40
 
72
- const cepLookup = new CepLookup({
73
- providers: [viaCepProvider],
74
- });
75
-
76
- // 1. Define your "mapper" function
77
- interface CustomAddress {
78
- postalCode: string;
79
- fullAddress: string;
80
- source: string;
41
+ ```ts
42
+ import {
43
+ CepLookup,
44
+ CepNotFoundError,
45
+ ProviderTimeoutError,
46
+ RateLimitError,
47
+ AllProvidersFailedError,
48
+ } from "@eusilvio/cep-lookup";
49
+
50
+ try {
51
+ await lookup.lookup("01001000");
52
+ } catch (error) {
53
+ if (error instanceof CepNotFoundError) {
54
+ console.log(error.code); // NOT_FOUND
55
+ } else if (error instanceof ProviderTimeoutError) {
56
+ console.log(error.code); // TIMEOUT
57
+ } else if (error instanceof RateLimitError) {
58
+ console.log(error.code); // RATE_LIMITED
59
+ } else if (error instanceof AllProvidersFailedError) {
60
+ console.log(error.code); // ALL_PROVIDERS_FAILED
61
+ }
81
62
  }
82
-
83
- const myMapper = (address: Address): CustomAddress => {
84
- return {
85
- postalCode: address.cep,
86
- fullAddress: `${address.street}, ${address.neighborhood} - ${address.city}/${address.state}`,
87
- source: address.service,
88
- };
89
- };
90
-
91
- // 2. Look up a CEP with the mapper
92
- cepLookup.lookup("01001-000", myMapper).then((customAddress: CustomAddress) => {
93
- console.log("Address found (custom format):", customAddress);
94
- // Output:
95
- // {
96
- // postalCode: '01001-000',
97
- // fullAddress: 'Praça da Sé, Sé - São Paulo/SP',
98
- // source: 'ViaCEP'
99
- // }
100
- });
101
63
  ```
102
64
 
103
- ### Example 3: Bulk CEP Lookup
104
-
105
- For scenarios where you need to query multiple CEPs at once, you can use the `lookupCeps` method. It processes the CEPs in parallel with a configurable concurrency limit to avoid overwhelming the providers.
106
-
107
- ```typescript
108
- import { CepLookup, BulkCepResult } from "@eusilvio/cep-lookup";
109
- import {
110
- viaCepProvider,
111
- brasilApiProvider,
112
- } from "@eusilvio/cep-lookup/providers";
113
-
114
- const cepsToLookup = ["01001-000", "99999-999", "04538-132"];
65
+ ## Circuit Breaker
115
66
 
116
- // 1. Create an instance with your settings
117
- const cepLookup = new CepLookup({
67
+ ```ts
68
+ const lookup = new CepLookup({
118
69
  providers: [viaCepProvider, brasilApiProvider],
119
- });
120
-
121
- // 2. Look up multiple CEPs
122
- cepLookup.lookupCeps(cepsToLookup, { concurrency: 2 }).then((results: BulkCepResult[]) => {
123
- console.log("Bulk lookup results:", results);
124
- // Output:
125
- // [
126
- // {
127
- // cep: '01001-000',
128
- // data: { cep: '01001-000', state: 'SP', city: 'São Paulo', ... },
129
- // provider: 'ViaCEP'
130
- // },
131
- // {
132
- // cep: '99999-999',
133
- // data: null,
134
- // error: [Error: All providers failed to find the CEP]
135
- // },
136
- // {
137
- // cep: '04538-132',
138
- // data: { cep: '04538-132', state: 'SP', city: 'São Paulo', ... },
139
- // provider: 'BrasilAPI'
140
- // }
141
- // ]
70
+ circuitBreaker: {
71
+ enabled: true,
72
+ failureThreshold: 3,
73
+ cooldownMs: 30_000,
74
+ },
142
75
  });
143
76
  ```
144
77
 
145
- ## API
146
-
147
- ### `new CepLookup(options)`
148
-
149
- Creates a new `CepLookup` instance.
150
-
151
- - `options`: A configuration object.
152
- - `providers` (Provider[], **required**): An array of providers that will be queried.
153
- - `fetcher` (Fetcher, _optional_): Your asynchronous function that fetches data from a URL. Defaults to global `fetch` if not provided.
154
- - `cache` (Cache, _optional_): An instance of a cache that implements the `Cache` interface. Use `InMemoryCache` for a simple in-memory cache.
155
- - `rateLimit` ({ requests: number, per: number }, _optional_): Configures an in-memory rate limiter (e.g., `{ requests: 10, per: 1000 }` for 10 requests per second).
156
- - `staggerDelay` (number, _optional_): Time in milliseconds to wait for the fastest provider before triggering backups (default: `100`).
157
-
158
- ### `cepLookup.warmup()`
159
-
160
- Pings all providers to determine the fastest one for the current environment. Call this during idle UI time (like when a user focuses a CEP input field) to optimize the subsequent `lookup` call.
161
-
162
- ### `cepLookup.lookup<T = Address>(cep, mapper?): Promise<T>`
163
-
164
- Returns a `Promise` that resolves to the address in the default format (`Address`) or in the custom format `T` if a `mapper` is provided.
165
-
166
- - `cep` (string, **required**): The CEP to be queried.
167
- - `mapper` ((address: Address) => T, _optional_): A function that receives the default `Address` object and transforms it into a new format `T`.
168
-
169
- ### `cepLookup.lookupCeps(ceps, options?): Promise<BulkCepResult[]>`
170
-
171
- Looks up multiple CEPs in bulk. Returns a `Promise` that resolves to an array of `BulkCepResult` objects, one for each queried CEP.
172
-
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`.
78
+ ## Health and SLA Metrics
79
+
80
+ ```ts
81
+ const health = lookup.getProviderHealth();
82
+ /*
83
+ [
84
+ {
85
+ provider: 'ViaCEP',
86
+ score: 0.94,
87
+ isOpen: false,
88
+ successCount: 12,
89
+ failureCount: 1,
90
+ avgLatencyMs: 52.11,
91
+ ...
92
+ }
93
+ ]
94
+ */
95
+
96
+ const metrics = lookup.getProviderMetrics();
97
+ /*
98
+ [
99
+ {
100
+ provider: 'ViaCEP',
101
+ requests: 13,
102
+ successes: 12,
103
+ failures: 1,
104
+ timeoutErrors: 0,
105
+ notFoundErrors: 1,
106
+ avgLatencyMs: 52.11
107
+ }
108
+ ]
109
+ */
110
+ ```
176
111
 
177
- > **Note on Deprecated Functions:**
178
- > Standalone `lookupCep` and `lookupCeps` functions are deprecated and will be removed in a future version. Please use the methods on a `CepLookup` instance instead.
112
+ ## Bulk Lookup
179
113
 
180
- ### Observability Events API
114
+ ```ts
115
+ const results = await lookup.lookupCeps(["01001-000", "99999-999"], 2);
116
+ ```
181
117
 
182
- Version 2.0.0 introduced an event-based API to monitor the library's behavior. You can listen to events to gather metrics on provider performance, latency, and errors.
118
+ ## API Summary
183
119
 
184
- ```typescript
185
- const cepLookup = new CepLookup({ providers: [...] });
120
+ ### `new CepLookup(options)`
186
121
 
187
- // Fired for each successful provider response
188
- cepLookup.on('success', ({ provider, cep, duration }) => {
189
- console.log(`[${provider}] Success for CEP ${cep} in ${duration}ms`);
190
- // myMetrics.timing('cep.latency', duration, { provider });
191
- });
122
+ - `providers`: required provider list.
123
+ - `fetcher`: optional custom HTTP fetch function.
124
+ - `cache`: optional cache implementation.
125
+ - `rateLimit`: `{ requests, per }`.
126
+ - `staggerDelay`: delay before backup providers.
127
+ - `retries`: retry count after failure.
128
+ - `retryDelay`: base retry delay in ms.
129
+ - `circuitBreaker`: `{ enabled, failureThreshold, cooldownMs }`.
192
130
 
193
- // Fired for each failed provider response
194
- cepLookup.on('failure', ({ provider, cep, error }) => {
195
- console.error(`[${provider}] Failure for CEP ${cep}: ${error.message}`);
196
- // myMetrics.increment('cep.failure', { provider });
197
- });
131
+ ### Methods
198
132
 
199
- // Fired when a CEP is resolved from the cache
200
- cepLookup.on('cache:hit', ({ cep }) => {
201
- console.log(`[Cache] CEP ${cep} found in cache.`);
202
- // myMetrics.increment('cep.cache_hit');
203
- });
133
+ - `lookup(cep, mapper?)`
134
+ - `lookupCeps(ceps, concurrency?)`
135
+ - `warmup()`
136
+ - `getProviderHealth()`
137
+ - `getProviderMetrics()`
138
+ - `on(event, listener)` / `off(event, listener)`
204
139
 
205
- // The lookup call remains the same
206
- cepLookup.lookup("01001-000");
207
- ```
140
+ ## Compatibility and support
208
141
 
209
- ## Examples
210
-
211
- You can find more detailed examples in the `examples/` directory:
212
-
213
- - **Basic Usage**: `examples/example.ts`
214
- - **Bulk Lookup**: `examples/bulk-example.ts`
215
- - **Custom Provider**: `examples/custom-provider-example.ts`
216
- - **Node.js Usage**: `examples/node-example.ts`
217
- - **React Component**: `examples/react-example.tsx`
218
- - **React Hook**: `examples/react-hook-example.ts`
219
- - **Angular Component/Service**: `examples/angular-example.ts`
220
- - **Cache Usage**: `examples/cache-example.ts`
221
-
222
- ## Creating a Custom Provider
223
-
224
- Your custom provider must always transform the API response to the library's default `Address` interface. The user's `mapper` will handle the final customization.
225
-
226
- ```typescript
227
- import { Provider, Address } from "@eusilvio/cep-lookup";
228
-
229
- const myCustomProvider: Provider = {
230
- name: "MyCustomAPI",
231
- buildUrl: (cep: string) => `https://myapi.com/cep/${cep}`,
232
- transform: (response: any): Address => {
233
- // Transforms the response from "MyCustomAPI" to the "Address" format
234
- return {
235
- cep: response.postal_code,
236
- state: response.data.state_short,
237
- city: response.data.city_name,
238
- neighborhood: response.data.neighborhood,
239
- street: response.data.street_name,
240
- service: "MyCustomAPI",
241
- };
242
- },
243
- };
244
- ```
142
+ - Node.js: `20.x`, `22.x`, `24.x`
143
+ - Maintenance policy: [SUPPORT.md](../../SUPPORT.md)
245
144
 
246
- ## Running Tests
145
+ ## Production docs
247
146
 
248
- ```bash
249
- npm test
250
- ```
147
+ - [Best Practices](../../docs/BEST_PRACTICES.md)
148
+ - [Migration Guide](../../docs/MIGRATION.md)
149
+ - [Cookbook](../../docs/COOKBOOK.md)
251
150
 
252
151
  ## License
253
152
 
254
- Distributed under the MIT License.
153
+ MIT
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 x=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var z=(i,e)=>{for(var t in e)x(i,t,{get:e[t],enumerable:!0})},V=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of q(e))!_.call(i,s)&&s!==t&&x(i,s,{get:()=>e[s],enumerable:!(r=U(e,s))||r.enumerable});return i};var H=i=>V(x({},"__esModule",{value:!0}),i);var G={};z(G,{AllProvidersFailedError:()=>p,CepLookup:()=>O,CepNotFoundError:()=>E,CepValidationError:()=>f,InMemoryCache:()=>A,ProviderTimeoutError:()=>v,ProviderUnavailableError:()=>P,RateLimitError:()=>g});module.exports=H(G);var A=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 f=class extends Error{constructor(t){super("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN.");this.code="INVALID_CEP";this.name="CepValidationError",this.cep=t}},g=class extends Error{constructor(t,r){super(`Rate limit exceeded: ${t} requests per ${r}ms.`);this.code="RATE_LIMITED";this.name="RateLimitError",this.limit=t,this.window=r}},v=class extends Error{constructor(t,r){super(`Timeout from ${t}`);this.code="TIMEOUT";this.name="ProviderTimeoutError",this.provider=t,this.timeout=r}},E=class extends Error{constructor(t,r){super("CEP not found");this.code="NOT_FOUND";this.name="CepNotFoundError",this.cep=t,this.provider=r}},p=class extends Error{constructor(t){super("All providers failed to resolve the CEP.");this.code="ALL_PROVIDERS_FAILED";this.name="AllProvidersFailedError",this.errors=t}},P=class extends Error{constructor(t){super(`Provider ${t} is temporarily unavailable (circuit open).`);this.code="PROVIDER_UNAVAILABLE";this.name="ProviderUnavailableError",this.provider=t}};function F(i,e,t){if(i instanceof Error){if(i instanceof f||i instanceof g||i instanceof v||i instanceof E||i instanceof P||i instanceof p)return i;let r=i.message?.toLowerCase?.()||"";return r.includes("cep not found")||r.includes("not found")||r.includes("status: 404")?new E(e,t):(i.name==="AbortError",i)}return new Error(String(i))}var S={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 M=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 $(i){if(!/^(\d{8}|\d{5}-\d{3})$/.test(i))throw new f(i);return i.replace("-","")}function K(i){let e={...i};return Object.keys(e).forEach(t=>{let r=e[t];typeof r=="string"&&(e[t]=r.trim())}),e}function j(i){if(!i.ddd&&i.state){let e=S[i.state];if(e)return{...i,ddd:e}}return i}var O=class{constructor(e){this.requestTimestamps=[];this.providerState=new Map;this.providers=e.providers,this.sortedProviders=[...e.providers],this.emitter=new M,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,this.retries=e.retries??0,this.retryDelay=e.retryDelay??1e3,this.logger=e.logger,this.circuitBreakerEnabled=e.circuitBreaker?.enabled??!0,this.circuitFailureThreshold=e.circuitBreaker?.failureThreshold??3,this.circuitCooldownMs=e.circuitBreaker?.cooldownMs??3e4,this.providers.forEach(t=>{this.providerState.set(t.name,{consecutiveFailures:0,successCount:0,failureCount:0,avgLatencyMs:0,requests:0,timeoutErrors:0,notFoundErrors:0})})}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 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);return this.sortedProviders=c.map(o=>o.provider).filter(o=>!!o),t.abort(),this.sortedProviders}getOrCreateProviderState(e){let t=this.providerState.get(e);if(t)return t;let r={consecutiveFailures:0,successCount:0,failureCount:0,avgLatencyMs:0,requests:0,timeoutErrors:0,notFoundErrors:0};return this.providerState.set(e,r),r}recordProviderSuccess(e,t){let r=this.getOrCreateProviderState(e);r.requests+=1,r.successCount+=1,r.consecutiveFailures=0;let s=r.successCount+r.failureCount;r.avgLatencyMs=s===1?t:(r.avgLatencyMs*(s-1)+t)/s,r.openUntil=void 0}recordProviderFailure(e,t,r){let s=this.getOrCreateProviderState(e);s.requests+=1,s.failureCount+=1,s.consecutiveFailures+=1;let c=s.successCount+s.failureCount;s.avgLatencyMs=c===1?t:(s.avgLatencyMs*(c-1)+t)/c,r instanceof v&&(s.timeoutErrors+=1),r instanceof E&&(s.notFoundErrors+=1),this.circuitBreakerEnabled&&s.consecutiveFailures>=this.circuitFailureThreshold&&(s.openUntil=Date.now()+this.circuitCooldownMs)}isProviderOpen(e){if(!this.circuitBreakerEnabled)return!1;let t=this.getOrCreateProviderState(e);return t.openUntil?Date.now()>=t.openUntil?(t.openUntil=void 0,t.consecutiveFailures=0,!1):!0:!1}scoreProvider(e){let t=this.getOrCreateProviderState(e.name),r=t.successCount+t.failureCount,s=r===0?1:t.successCount/r,c=t.avgLatencyMs>0?Math.min(t.avgLatencyMs/1e3,1):0,o=this.isProviderOpen(e.name)?1:0;return s*.8+(1-c)*.2-o}getProviderHealth(){return this.providers.map(e=>{let t=this.getOrCreateProviderState(e.name);return{provider:e.name,score:Number(this.scoreProvider(e).toFixed(4)),isOpen:this.isProviderOpen(e.name),openUntil:t.openUntil,consecutiveFailures:t.consecutiveFailures,successCount:t.successCount,failureCount:t.failureCount,avgLatencyMs:Number(t.avgLatencyMs.toFixed(2))}}).sort((e,t)=>t.score-e.score)}getProviderMetrics(){return this.providers.map(e=>{let t=this.getOrCreateProviderState(e.name);return{provider:e.name,requests:t.requests,successes:t.successCount,failures:t.failureCount,timeoutErrors:t.timeoutErrors,notFoundErrors:t.notFoundErrors,avgLatencyMs:Number(t.avgLatencyMs.toFixed(2))}})}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=$(e);if(this.log("lookup:start",{cep:r}),this.cache){let o=this.cache.get(r);if(o)return this.log("cache:hit",{cep:r}),this.emitter.emit("cache:hit",{cep:r}),t?t(o):o}let s,c=1+this.retries;for(let o=0;o<c;o++){if(o>0){let a=this.retryDelay*Math.pow(2,o-1);this.log("retry:attempt",{attempt:o,cep:r,delay:a}),await new Promise(u=>setTimeout(u,a))}try{return await this._lookupFromProviders(r,t)}catch(a){if(a instanceof f||a instanceof g)throw a;s=a}}throw s}async _lookupFromProviders(e,t){let r=new AbortController,{signal:s}=r,c=this.sortedProviders.filter(n=>!this.isProviderOpen(n.name)),o=[...c].sort((n,l)=>this.scoreProvider(l)-this.scoreProvider(n)),a=o.length>0?o:[...this.sortedProviders].sort((n,l)=>this.scoreProvider(l)-this.scoreProvider(n));if(a.length===0)throw new p([new P("all")]);if(c.length===0&&this.circuitBreakerEnabled)throw new p(a.map(n=>new P(n.name)));let u=n=>{let l=Date.now(),L=n.buildUrl(e);this.log("provider:start",{provider:n.name,cep:e});let I=new Promise((b,m)=>{if(!n.timeout)return;let d=setTimeout(()=>{s.removeEventListener("abort",R);let N=Date.now()-l,T=new v(n.name,n.timeout);this.recordProviderFailure(n.name,N,T),this.log("provider:failure",{provider:n.name,cep:e,error:T.message}),this.emitter.emit("failure",{provider:n.name,cep:e,duration:N,error:T}),m(T)},n.timeout),R=()=>clearTimeout(d);s.addEventListener("abort",R,{once:!0})}),B=this.fetcher(L,s).then(b=>n.transform(b)).then(b=>{let m=Date.now()-l,d=j(K(b));return this.recordProviderSuccess(n.name,m),this.log("provider:success",{provider:n.name,cep:e,duration:m}),this.emitter.emit("success",{provider:n.name,cep:e,duration:m,address:d}),this.cache&&this.cache.set(e,d),t?t(d):d}).catch(b=>{let m=Date.now()-l,d=F(b,e,n.name);throw d.name!=="AbortError"&&!(d instanceof v)&&(this.recordProviderFailure(n.name,m,d),this.log("provider:failure",{provider:n.name,cep:e,error:d.message}),this.emitter.emit("failure",{provider:n.name,cep:e,duration:m,error:d})),d});return Promise.race([B,I])},y=a[0],h=a.slice(1);if(h.length===0)try{return await u(y)}finally{r.abort()}let w=null,C=null,k=new Promise((n,l)=>{C=()=>{if(w&&clearTimeout(w),s.aborted)return;let L=h.map(u);Promise.any(L).then(n).catch(l)},w=setTimeout(C,this.staggerDelay)}),D=u(y).catch(n=>{throw C&&C(),n});try{return await Promise.any([D,k])}catch(n){let l=n.errors||[n];throw new p(l)}finally{w&&clearTimeout(w),r.abort()}}async lookupCeps(e,t=5,r){if(!e||e.length===0)return[];let s=new Array(e.length),c=0,o=async()=>{for(;c<e.length;){let u=c++;if(u>=e.length)break;let y=e[u];try{let h=await this.lookup(y);if(h)s[u]={cep:y,data:r?r(h):h,provider:h.service};else throw new Error("No address found")}catch(h){s[u]={cep:y,data:null,error:h}}}},a=Array.from({length:Math.min(t,e.length)},()=>o());return await Promise.all(a),s.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 L=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 E=class extends Error{constructor(t){super("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN.");this.code="INVALID_CEP";this.name="CepValidationError",this.cep=t}},P=class extends Error{constructor(t,r){super(`Rate limit exceeded: ${t} requests per ${r}ms.`);this.code="RATE_LIMITED";this.name="RateLimitError",this.limit=t,this.window=r}},v=class extends Error{constructor(t,r){super(`Timeout from ${t}`);this.code="TIMEOUT";this.name="ProviderTimeoutError",this.provider=t,this.timeout=r}},y=class extends Error{constructor(t,r){super("CEP not found");this.code="NOT_FOUND";this.name="CepNotFoundError",this.cep=t,this.provider=r}},p=class extends Error{constructor(t){super("All providers failed to resolve the CEP.");this.code="ALL_PROVIDERS_FAILED";this.name="AllProvidersFailedError",this.errors=t}},b=class extends Error{constructor(t){super(`Provider ${t} is temporarily unavailable (circuit open).`);this.code="PROVIDER_UNAVAILABLE";this.name="ProviderUnavailableError",this.provider=t}};function R(i,e,t){if(i instanceof Error){if(i instanceof E||i instanceof P||i instanceof v||i instanceof y||i instanceof b||i instanceof p)return i;let r=i.message?.toLowerCase?.()||"";return r.includes("cep not found")||r.includes("not found")||r.includes("status: 404")?new y(e,t):(i.name==="AbortError",i)}return new Error(String(i))}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(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 E(i);return i.replace("-","")}function U(i){let e={...i};return Object.keys(e).forEach(t=>{let r=e[t];typeof r=="string"&&(e[t]=r.trim())}),e}function q(i){if(!i.ddd&&i.state){let e=N[i.state];if(e)return{...i,ddd:e}}return i}var F=class{constructor(e){this.requestTimestamps=[];this.providerState=new Map;this.providers=e.providers,this.sortedProviders=[...e.providers],this.emitter=new x,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,this.retries=e.retries??0,this.retryDelay=e.retryDelay??1e3,this.logger=e.logger,this.circuitBreakerEnabled=e.circuitBreaker?.enabled??!0,this.circuitFailureThreshold=e.circuitBreaker?.failureThreshold??3,this.circuitCooldownMs=e.circuitBreaker?.cooldownMs??3e4,this.providers.forEach(t=>{this.providerState.set(t.name,{consecutiveFailures:0,successCount:0,failureCount:0,avgLatencyMs:0,requests:0,timeoutErrors:0,notFoundErrors:0})})}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 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);return this.sortedProviders=c.map(o=>o.provider).filter(o=>!!o),t.abort(),this.sortedProviders}getOrCreateProviderState(e){let t=this.providerState.get(e);if(t)return t;let r={consecutiveFailures:0,successCount:0,failureCount:0,avgLatencyMs:0,requests:0,timeoutErrors:0,notFoundErrors:0};return this.providerState.set(e,r),r}recordProviderSuccess(e,t){let r=this.getOrCreateProviderState(e);r.requests+=1,r.successCount+=1,r.consecutiveFailures=0;let s=r.successCount+r.failureCount;r.avgLatencyMs=s===1?t:(r.avgLatencyMs*(s-1)+t)/s,r.openUntil=void 0}recordProviderFailure(e,t,r){let s=this.getOrCreateProviderState(e);s.requests+=1,s.failureCount+=1,s.consecutiveFailures+=1;let c=s.successCount+s.failureCount;s.avgLatencyMs=c===1?t:(s.avgLatencyMs*(c-1)+t)/c,r instanceof v&&(s.timeoutErrors+=1),r instanceof y&&(s.notFoundErrors+=1),this.circuitBreakerEnabled&&s.consecutiveFailures>=this.circuitFailureThreshold&&(s.openUntil=Date.now()+this.circuitCooldownMs)}isProviderOpen(e){if(!this.circuitBreakerEnabled)return!1;let t=this.getOrCreateProviderState(e);return t.openUntil?Date.now()>=t.openUntil?(t.openUntil=void 0,t.consecutiveFailures=0,!1):!0:!1}scoreProvider(e){let t=this.getOrCreateProviderState(e.name),r=t.successCount+t.failureCount,s=r===0?1:t.successCount/r,c=t.avgLatencyMs>0?Math.min(t.avgLatencyMs/1e3,1):0,o=this.isProviderOpen(e.name)?1:0;return s*.8+(1-c)*.2-o}getProviderHealth(){return this.providers.map(e=>{let t=this.getOrCreateProviderState(e.name);return{provider:e.name,score:Number(this.scoreProvider(e).toFixed(4)),isOpen:this.isProviderOpen(e.name),openUntil:t.openUntil,consecutiveFailures:t.consecutiveFailures,successCount:t.successCount,failureCount:t.failureCount,avgLatencyMs:Number(t.avgLatencyMs.toFixed(2))}}).sort((e,t)=>t.score-e.score)}getProviderMetrics(){return this.providers.map(e=>{let t=this.getOrCreateProviderState(e.name);return{provider:e.name,requests:t.requests,successes:t.successCount,failures:t.failureCount,timeoutErrors:t.timeoutErrors,notFoundErrors:t.notFoundErrors,avgLatencyMs:Number(t.avgLatencyMs.toFixed(2))}})}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 P(this.rateLimit.requests,this.rateLimit.per);this.requestTimestamps.push(e)}async lookup(e,t){this.checkRateLimit();let r=B(e);if(this.log("lookup:start",{cep:r}),this.cache){let o=this.cache.get(r);if(o)return this.log("cache:hit",{cep:r}),this.emitter.emit("cache:hit",{cep:r}),t?t(o):o}let s,c=1+this.retries;for(let o=0;o<c;o++){if(o>0){let a=this.retryDelay*Math.pow(2,o-1);this.log("retry:attempt",{attempt:o,cep:r,delay:a}),await new Promise(u=>setTimeout(u,a))}try{return await this._lookupFromProviders(r,t)}catch(a){if(a instanceof E||a instanceof P)throw a;s=a}}throw s}async _lookupFromProviders(e,t){let r=new AbortController,{signal:s}=r,c=this.sortedProviders.filter(n=>!this.isProviderOpen(n.name)),o=[...c].sort((n,l)=>this.scoreProvider(l)-this.scoreProvider(n)),a=o.length>0?o:[...this.sortedProviders].sort((n,l)=>this.scoreProvider(l)-this.scoreProvider(n));if(a.length===0)throw new p([new b("all")]);if(c.length===0&&this.circuitBreakerEnabled)throw new p(a.map(n=>new b(n.name)));let u=n=>{let l=Date.now(),A=n.buildUrl(e);this.log("provider:start",{provider:n.name,cep:e});let D=new Promise((g,m)=>{if(!n.timeout)return;let d=setTimeout(()=>{s.removeEventListener("abort",M);let O=Date.now()-l,T=new v(n.name,n.timeout);this.recordProviderFailure(n.name,O,T),this.log("provider:failure",{provider:n.name,cep:e,error:T.message}),this.emitter.emit("failure",{provider:n.name,cep:e,duration:O,error:T}),m(T)},n.timeout),M=()=>clearTimeout(d);s.addEventListener("abort",M,{once:!0})}),I=this.fetcher(A,s).then(g=>n.transform(g)).then(g=>{let m=Date.now()-l,d=q(U(g));return this.recordProviderSuccess(n.name,m),this.log("provider:success",{provider:n.name,cep:e,duration:m}),this.emitter.emit("success",{provider:n.name,cep:e,duration:m,address:d}),this.cache&&this.cache.set(e,d),t?t(d):d}).catch(g=>{let m=Date.now()-l,d=R(g,e,n.name);throw d.name!=="AbortError"&&!(d instanceof v)&&(this.recordProviderFailure(n.name,m,d),this.log("provider:failure",{provider:n.name,cep:e,error:d.message}),this.emitter.emit("failure",{provider:n.name,cep:e,duration:m,error:d})),d});return Promise.race([I,D])},f=a[0],h=a.slice(1);if(h.length===0)try{return await u(f)}finally{r.abort()}let w=null,C=null,S=new Promise((n,l)=>{C=()=>{if(w&&clearTimeout(w),s.aborted)return;let A=h.map(u);Promise.any(A).then(n).catch(l)},w=setTimeout(C,this.staggerDelay)}),k=u(f).catch(n=>{throw C&&C(),n});try{return await Promise.any([k,S])}catch(n){let l=n.errors||[n];throw new p(l)}finally{w&&clearTimeout(w),r.abort()}}async lookupCeps(e,t=5,r){if(!e||e.length===0)return[];let s=new Array(e.length),c=0,o=async()=>{for(;c<e.length;){let u=c++;if(u>=e.length)break;let f=e[u];try{let h=await this.lookup(f);if(h)s[u]={cep:f,data:r?r(h):h,provider:h.service};else throw new Error("No address found")}catch(h){s[u]={cep:f,data:null,error:h}}}},a=Array.from({length:Math.min(t,e.length)},()=>o());return await Promise.all(a),s.filter(Boolean)}};export{p as AllProvidersFailedError,F as CepLookup,y as CepNotFoundError,E as CepValidationError,L as InMemoryCache,v as ProviderTimeoutError,b as ProviderUnavailableError,P 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
  }