@eusilvio/cep-lookup 2.0.0 → 2.0.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/README.md CHANGED
@@ -149,6 +149,7 @@ Creates a new `CepLookup` instance.
149
149
  - `providers` (Provider[], **required**): An array of providers that will be queried.
150
150
  - `fetcher` (Fetcher, _optional_): Your asynchronous function that fetches data from a URL. Defaults to global `fetch` if not provided.
151
151
  - `cache` (Cache, _optional_): An instance of a cache that implements the `Cache` interface. Use `InMemoryCache` for a simple in-memory cache.
152
+ - `rateLimit` ({ requests: number, per: number }, _optional_): Configures an in-memory rate limiter (e.g., `{ requests: 10, per: 1000 }` for 10 requests per second).
152
153
 
153
154
  ### `cepLookup.lookup<T = Address>(cep, mapper?): Promise<T>`
154
155
 
@@ -168,6 +169,35 @@ Looks up multiple CEPs in bulk. Returns a `Promise` that resolves to an array of
168
169
  - `fetcher` (Fetcher, _optional_): A custom fetch function.
169
170
  - `cache` (Cache, _optional_): A cache instance.
170
171
 
172
+ ### Observability Events API
173
+
174
+ 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.
175
+
176
+ ```typescript
177
+ const cepLookup = new CepLookup({ providers: [...] });
178
+
179
+ // Fired for each successful provider response
180
+ cepLookup.on('success', ({ provider, cep, duration }) => {
181
+ console.log(`[${provider}] Success for CEP ${cep} in ${duration}ms`);
182
+ // myMetrics.timing('cep.latency', duration, { provider });
183
+ });
184
+
185
+ // Fired for each failed provider response
186
+ cepLookup.on('failure', ({ provider, cep, error }) => {
187
+ console.error(`[${provider}] Failure for CEP ${cep}: ${error.message}`);
188
+ // myMetrics.increment('cep.failure', { provider });
189
+ });
190
+
191
+ // Fired when a CEP is resolved from the cache
192
+ cepLookup.on('cache:hit', ({ cep }) => {
193
+ console.log(`[Cache] CEP ${cep} found in cache.`);
194
+ // myMetrics.increment('cep.cache_hit');
195
+ });
196
+
197
+ // The lookup call remains the same
198
+ cepLookup.lookup("01001-000");
199
+ ```
200
+
171
201
  ## Examples
172
202
 
173
203
  You can find more detailed examples in the `examples/` directory:
package/dist/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
- import { Address, Fetcher, Provider, CepLookupOptions, BulkCepResult, RateLimitOptions } from "./types";
1
+ import { Address, Fetcher, Provider, CepLookupOptions, BulkCepResult, RateLimitOptions, EventName, EventListener, EventMap } from "./types";
2
2
  import { Cache, InMemoryCache } from "./cache";
3
- export { Address, Fetcher, Provider, CepLookupOptions, Cache, InMemoryCache, BulkCepResult, RateLimitOptions };
3
+ export { Address, Fetcher, Provider, CepLookupOptions, Cache, InMemoryCache, BulkCepResult, RateLimitOptions, EventName, EventListener, EventMap };
4
4
  /**
5
5
  * @class CepLookup
6
6
  * @description A class for looking up Brazilian postal codes (CEPs) using multiple providers.
7
- * It queries multiple services simultaneously and returns the response from the fastest one.
8
7
  */
9
8
  export declare class CepLookup {
10
9
  private providers;
@@ -12,46 +11,17 @@ export declare class CepLookup {
12
11
  private cache?;
13
12
  private rateLimit?;
14
13
  private requestTimestamps;
15
- /**
16
- * @constructor
17
- * @param {CepLookupOptions} options - The options for initializing the CepLookup instance.
18
- */
14
+ private emitter;
19
15
  constructor(options: CepLookupOptions);
16
+ on<T extends EventName>(eventName: T, listener: EventListener<T>): void;
17
+ off<T extends EventName>(eventName: T, listener: EventListener<T>): void;
20
18
  private checkRateLimit;
21
- /**
22
- * @method lookup
23
- * @description Looks up an address for a given CEP.
24
- * @template T - The expected return type, defaults to `Address`.
25
- * @param {string} cep - The CEP to be queried.
26
- * @param {(address: Address) => T} [mapper] - An optional function to transform the `Address` object into a custom format `T`.
27
- * @returns {Promise<T>} A Promise that resolves to the address in the default `Address` format or a custom format `T` if a mapper is provided.
28
- * @throws {Error} If the CEP is invalid or if all providers fail to find the CEP.
29
- */
30
19
  lookup<T = Address>(cep: string, mapper?: (address: Address) => T): Promise<T>;
31
20
  }
32
- /**
33
- * @function lookupCep
34
- * @description Backward-compatible function for looking up a CEP. Internally creates a `CepLookup` instance.
35
- * @template T - The expected return type, defaults to `Address`.
36
- * @param {object} options - Options for the lookup.
37
- * @param {string} options.cep - The CEP to be queried.
38
- * @param {Provider[]} options.providers - An array of `Provider` instances.
39
- * @param {Fetcher} [options.fetcher] - The `Fetcher` function. Defaults to global `fetch` if not provided.
40
- * @param {Cache} [options.cache] - The `Cache` instance.
41
- * @param {(address: Address) => T} [options.mapper] - An optional function to transform the `Address` object.
42
- * @returns {Promise<T>} A Promise that resolves to the address.
43
- * @throws {Error} If the CEP is invalid or if all providers fail.
44
- */
45
21
  export declare function lookupCep<T = Address>(options: CepLookupOptions & {
46
22
  cep: string;
47
23
  mapper?: (address: Address) => T;
48
24
  }): Promise<T>;
49
- /**
50
- * @function lookupCeps
51
- * @description Looks up multiple CEPs in bulk with controlled concurrency.
52
- * @param {CepLookupOptions & { ceps: string[], concurrency?: number }} options - Options for the bulk lookup.
53
- * @returns {Promise<BulkCepResult[]>} A Promise that resolves to an array of results for each CEP.
54
- */
55
25
  export declare function lookupCeps(options: CepLookupOptions & {
56
26
  ceps: string[];
57
27
  concurrency?: number;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var g=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var L=(r,e)=>{for(var t in e)g(r,t,{get:e[t],enumerable:!0})},y=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of k(e))!A.call(r,s)&&s!==t&&g(r,s,{get:()=>e[s],enumerable:!(i=v(e,s))||i.enumerable});return r};var T=r=>y(g({},"__esModule",{value:!0}),r);var x={};L(x,{CepLookup:()=>h,InMemoryCache:()=>m,lookupCep:()=>P,lookupCeps:()=>b});module.exports=T(x);var m=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()}};function N(r){if(!/^(\d{8}|\d{5}-\d{3})$/.test(r))throw new Error("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN.");return r.replace("-","")}function C(r){let e={...r};for(let t in e)typeof e[t]=="string"&&(e[t]=e[t].trim());return e}var h=class{constructor(e){this.requestTimestamps=[];this.providers=e.providers,this.fetcher=e.fetcher||(async(t,i)=>{let s=await fetch(t,{signal:i});if(!s.ok)throw new Error(`HTTP error! status: ${s.status}`);return s.json()}),this.cache=e.cache,this.rateLimit=e.rateLimit}checkRateLimit(){if(!this.rateLimit)return;let e=Date.now(),t=e-this.rateLimit.per;if(this.requestTimestamps=this.requestTimestamps.filter(i=>i>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 i=N(e);if(this.cache){let o=this.cache.get(i);if(o)return Promise.resolve(t?t(o):o)}let s=new AbortController,{signal:a}=s,u=this.providers.map(o=>{let p=o.buildUrl(i),l=new Promise((c,n)=>{let d,w=()=>{clearTimeout(d),n(new DOMException("Aborted","AbortError"))};o.timeout?(d=setTimeout(()=>{a.removeEventListener("abort",w),n(new Error(`Timeout from ${o.name}`))},o.timeout),a.addEventListener("abort",w,{once:!0})):a.addEventListener("abort",w,{once:!0})}),f=this.fetcher(p,a).then(c=>o.transform(c)).then(c=>{let n=C(c);return this.cache&&this.cache.set(i,n),t?t(n):n});return Promise.race([f,l])});try{return await Promise.any(u)}finally{s.abort()}}};function P(r){let{cep:e,providers:t,fetcher:i,mapper:s,cache:a}=r;return new h({providers:t,fetcher:i,cache:a}).lookup(e,s)}async function b(r){let{ceps:e,providers:t,fetcher:i,cache:s,concurrency:a=5}=r;if(!e||e.length===0)return[];let u=new h({providers:t,fetcher:i,cache:s}),o=new Array(e.length),p=0,l=async()=>{for(;p<e.length;){let c=p++;if(c>=e.length)break;let n=e[c];try{let d=await u.lookup(n);if(d)o[c]={cep:n,data:d,provider:d.service};else throw new Error("No address found")}catch(d){o[c]={cep:n,data:null,error:d}}}},f=Array.from({length:Math.min(a,e.length)},()=>l());return await Promise.all(f),o.filter(Boolean)}0&&(module.exports={CepLookup,InMemoryCache,lookupCep,lookupCeps});
1
+ "use strict";var w=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var N=(r,e)=>{for(var t in e)w(r,t,{get:e[t],enumerable:!0})},x=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of A(e))!y.call(r,i)&&i!==t&&w(r,i,{get:()=>e[i],enumerable:!(s=E(e,i))||s.enumerable});return r};var C=r=>x(w({},"__esModule",{value:!0}),r);var O={};N(O,{CepLookup:()=>m,InMemoryCache:()=>l,lookupCep:()=>R,lookupCeps:()=>q});module.exports=C(O);var l=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 T=class{constructor(){this.listeners={}}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].push(t)}off(e,t){this.listeners[e]&&(this.listeners[e]=this.listeners[e].filter(s=>s!==t))}emit(e,t){this.listeners[e]&&this.listeners[e].forEach(s=>s(t))}};function b(r){if(!/^(\d{8}|\d{5}-\d{3})$/.test(r))throw new Error("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN.");return r.replace("-","")}function P(r){let e={...r};for(let t in e)typeof e[t]=="string"&&(e[t]=e[t].trim());return e}var m=class{constructor(e){this.requestTimestamps=[];this.providers=e.providers,this.emitter=new T,this.fetcher=e.fetcher||(async(t,s)=>{let i=await fetch(t,{signal:s});if(!i.ok)throw new Error(`HTTP error! status: ${i.status}`);return i.json()}),this.cache=e.cache,this.rateLimit=e.rateLimit}on(e,t){this.emitter.on(e,t)}off(e,t){this.emitter.off(e,t)}checkRateLimit(){if(!this.rateLimit)return;let e=Date.now(),t=e-this.rateLimit.per;if(this.requestTimestamps=this.requestTimestamps.filter(s=>s>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 s=b(e);if(this.cache){let n=this.cache.get(s);if(n)return this.emitter.emit("cache:hit",{cep:s}),Promise.resolve(t?t(n):n)}let i=new AbortController,{signal:d}=i,h=this.providers.map(n=>{let u=Date.now(),p=n.buildUrl(s),f=new Promise((o,c)=>{if(!n.timeout)return;let a=setTimeout(()=>{d.removeEventListener("abort",g);let k=Date.now()-u,L=new Error(`Timeout from ${n.name}`);this.emitter.emit("failure",{provider:n.name,cep:s,duration:k,error:L}),c(L)},n.timeout),g=()=>clearTimeout(a);d.addEventListener("abort",g,{once:!0})}),v=this.fetcher(p,d).then(o=>n.transform(o)).then(o=>{let c=Date.now()-u,a=P(o);return this.emitter.emit("success",{provider:n.name,cep:s,duration:c,address:a}),this.cache&&this.cache.set(s,a),t?t(a):a}).catch(o=>{let c=Date.now()-u;throw o.message.includes("Timeout from")||this.emitter.emit("failure",{provider:n.name,cep:s,duration:c,error:o}),o});return Promise.race([v,f])});try{return h.length===1?await h[0]:await Promise.any(h)}finally{i.abort()}}};function R(r){let{cep:e,providers:t,fetcher:s,mapper:i,cache:d,rateLimit:h}=r;return new m({providers:t,fetcher:s,cache:d,rateLimit:h}).lookup(e,i)}async function q(r){let{ceps:e,providers:t,fetcher:s,cache:i,concurrency:d=5,rateLimit:h}=r;if(!e||e.length===0)return[];let n=new m({providers:t,fetcher:s,cache:i,rateLimit:h}),u=new Array(e.length),p=0,f=async()=>{for(;p<e.length;){let o=p++;if(o>=e.length)break;let c=e[o];try{let a=await n.lookup(c);if(a)u[o]={cep:c,data:a,provider:a.service};else throw new Error("No address found")}catch(a){u[o]={cep:c,data:null,error:a}}}},v=Array.from({length:Math.min(d,e.length)},()=>f());return await Promise.all(v),u.filter(Boolean)}0&&(module.exports={CepLookup,InMemoryCache,lookupCep,lookupCeps});
package/dist/types.d.ts CHANGED
@@ -1,12 +1,7 @@
1
+ import { Cache } from "./cache";
1
2
  /**
2
3
  * @interface Address
3
4
  * @description Represents a standardized address object returned by the CEP lookup.
4
- * @property {string} cep - The postal code.
5
- * @property {string} state - The state abbreviation (e.g., 'SP', 'RJ').
6
- * @property {string} city - The city name.
7
- * @property {string} neighborhood - The neighborhood name.
8
- * @property {string} street - The street name.
9
- * @property {string} service - The name of the service that provided the address (e.g., 'ViaCEP', 'BrasilAPI').
10
5
  */
11
6
  export interface Address {
12
7
  cep: string;
@@ -19,9 +14,6 @@ export interface Address {
19
14
  /**
20
15
  * @interface Provider
21
16
  * @description Defines the contract for a CEP lookup provider.
22
- * @property {string} name - The name of the provider.
23
- * @property {(cep: string) => string} buildUrl - A function that constructs the API URL for a given CEP.
24
- * @property {(response: any) => Address} transform - A function that transforms the raw API response into a standardized `Address` object.
25
17
  */
26
18
  export interface Provider {
27
19
  name: string;
@@ -31,39 +23,30 @@ export interface Provider {
31
23
  }
32
24
  /**
33
25
  * @typedef {function(url: string, signal?: AbortSignal): Promise<any>}
34
- * @description A function that fetches data from a given URL and returns a Promise resolving to the response data.
26
+ * @description A function that fetches data from a given URL.
35
27
  */
36
28
  export type Fetcher = (url: string, signal?: AbortSignal) => Promise<any>;
29
+ /**
30
+ * @interface RateLimitOptions
31
+ * @description Options for configuring the internal rate limiter.
32
+ */
33
+ export interface RateLimitOptions {
34
+ requests: number;
35
+ per: number;
36
+ }
37
37
  /**
38
38
  * @interface CepLookupOptions
39
39
  * @description Options for initializing the `CepLookup` class.
40
- * @property {Provider[]} providers - An array of `Provider` instances to be used for CEP lookup.
41
- * @property {Fetcher} [fetcher] - The `Fetcher` function to be used for making HTTP requests. Defaults to global `fetch` if not provided.
42
40
  */
43
- import { Cache } from "./cache";
44
41
  export interface CepLookupOptions {
45
42
  providers: Provider[];
46
43
  fetcher?: Fetcher;
47
44
  cache?: Cache;
48
45
  rateLimit?: RateLimitOptions;
49
46
  }
50
- /**
51
- * @interface RateLimitOptions
52
- * @description Options for configuring the internal rate limiter.
53
- * @property {number} requests - The maximum number of requests allowed within the time window.
54
- * @property {number} per - The time window in milliseconds.
55
- */
56
- export interface RateLimitOptions {
57
- requests: number;
58
- per: number;
59
- }
60
47
  /**
61
48
  * @interface BulkCepResult
62
49
  * @description Represents the result for a single CEP in a bulk lookup operation.
63
- * @property {string} cep - The original CEP string.
64
- * @property {Address | null} data - The address data if the lookup was successful, otherwise null.
65
- * @property {string} [provider] - The name of the provider that successfully resolved the address.
66
- * @property {Error} [error] - An error object if the lookup failed for this specific CEP.
67
50
  */
68
51
  export interface BulkCepResult {
69
52
  cep: string;
@@ -71,3 +54,25 @@ export interface BulkCepResult {
71
54
  provider?: string;
72
55
  error?: Error;
73
56
  }
57
+ export type EventName = 'success' | 'failure' | 'cache:hit';
58
+ export interface SuccessPayload {
59
+ provider: string;
60
+ cep: string;
61
+ duration: number;
62
+ address: Address;
63
+ }
64
+ export interface FailurePayload {
65
+ provider: string;
66
+ cep: string;
67
+ duration: number;
68
+ error: Error;
69
+ }
70
+ export interface CacheHitPayload {
71
+ cep: string;
72
+ }
73
+ export interface EventMap {
74
+ success: SuccessPayload;
75
+ failure: FailurePayload;
76
+ 'cache:hit': CacheHitPayload;
77
+ }
78
+ export type EventListener<T extends EventName> = (payload: EventMap[T]) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eusilvio/cep-lookup",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "A modern, flexible, and agnostic CEP lookup library written in TypeScript.",
5
5
  "license": "MIT",
6
6
  "repository": {