@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.
@@ -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,35 @@
1
+ export type CepErrorCode = "INVALID_CEP" | "RATE_LIMITED" | "TIMEOUT" | "NOT_FOUND" | "PROVIDER_UNAVAILABLE" | "ALL_PROVIDERS_FAILED" | "UNKNOWN";
2
+ export declare class CepValidationError extends Error {
3
+ readonly cep: string;
4
+ readonly code: CepErrorCode;
5
+ constructor(cep: string);
6
+ }
7
+ export declare class RateLimitError extends Error {
8
+ readonly limit: number;
9
+ readonly window: number;
10
+ readonly code: CepErrorCode;
11
+ constructor(limit: number, window: number);
12
+ }
13
+ export declare class ProviderTimeoutError extends Error {
14
+ readonly provider: string;
15
+ readonly timeout: number;
16
+ readonly code: CepErrorCode;
17
+ constructor(provider: string, timeout: number);
18
+ }
19
+ export declare class CepNotFoundError extends Error {
20
+ readonly cep: string;
21
+ readonly provider?: string;
22
+ readonly code: CepErrorCode;
23
+ constructor(cep: string, provider?: string);
24
+ }
25
+ export declare class AllProvidersFailedError extends Error {
26
+ readonly errors: Error[];
27
+ readonly code: CepErrorCode;
28
+ constructor(errors: Error[]);
29
+ }
30
+ export declare class ProviderUnavailableError extends Error {
31
+ readonly provider: string;
32
+ readonly code: CepErrorCode;
33
+ constructor(provider: string);
34
+ }
35
+ export declare function normalizeProviderError(error: unknown, cep: string, provider: string): Error;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProviderUnavailableError = exports.AllProvidersFailedError = exports.CepNotFoundError = exports.ProviderTimeoutError = exports.RateLimitError = exports.CepValidationError = void 0;
4
+ exports.normalizeProviderError = normalizeProviderError;
5
+ class CepValidationError extends Error {
6
+ constructor(cep) {
7
+ super("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN.");
8
+ this.code = "INVALID_CEP";
9
+ this.name = "CepValidationError";
10
+ this.cep = cep;
11
+ }
12
+ }
13
+ exports.CepValidationError = CepValidationError;
14
+ class RateLimitError extends Error {
15
+ constructor(limit, window) {
16
+ super(`Rate limit exceeded: ${limit} requests per ${window}ms.`);
17
+ this.code = "RATE_LIMITED";
18
+ this.name = "RateLimitError";
19
+ this.limit = limit;
20
+ this.window = window;
21
+ }
22
+ }
23
+ exports.RateLimitError = RateLimitError;
24
+ class ProviderTimeoutError extends Error {
25
+ constructor(provider, timeout) {
26
+ super(`Timeout from ${provider}`);
27
+ this.code = "TIMEOUT";
28
+ this.name = "ProviderTimeoutError";
29
+ this.provider = provider;
30
+ this.timeout = timeout;
31
+ }
32
+ }
33
+ exports.ProviderTimeoutError = ProviderTimeoutError;
34
+ class CepNotFoundError extends Error {
35
+ constructor(cep, provider) {
36
+ super("CEP not found");
37
+ this.code = "NOT_FOUND";
38
+ this.name = "CepNotFoundError";
39
+ this.cep = cep;
40
+ this.provider = provider;
41
+ }
42
+ }
43
+ exports.CepNotFoundError = CepNotFoundError;
44
+ class AllProvidersFailedError extends Error {
45
+ constructor(errors) {
46
+ super("All providers failed to resolve the CEP.");
47
+ this.code = "ALL_PROVIDERS_FAILED";
48
+ this.name = "AllProvidersFailedError";
49
+ this.errors = errors;
50
+ }
51
+ }
52
+ exports.AllProvidersFailedError = AllProvidersFailedError;
53
+ class ProviderUnavailableError extends Error {
54
+ constructor(provider) {
55
+ super(`Provider ${provider} is temporarily unavailable (circuit open).`);
56
+ this.code = "PROVIDER_UNAVAILABLE";
57
+ this.name = "ProviderUnavailableError";
58
+ this.provider = provider;
59
+ }
60
+ }
61
+ exports.ProviderUnavailableError = ProviderUnavailableError;
62
+ function normalizeProviderError(error, cep, provider) {
63
+ if (error instanceof Error) {
64
+ if (error instanceof CepValidationError ||
65
+ error instanceof RateLimitError ||
66
+ error instanceof ProviderTimeoutError ||
67
+ error instanceof CepNotFoundError ||
68
+ error instanceof ProviderUnavailableError ||
69
+ error instanceof AllProvidersFailedError) {
70
+ return error;
71
+ }
72
+ const message = error.message?.toLowerCase?.() || "";
73
+ if (message.includes("cep not found") || message.includes("not found") || message.includes("status: 404")) {
74
+ return new CepNotFoundError(cep, provider);
75
+ }
76
+ if (error.name === "AbortError") {
77
+ return error;
78
+ }
79
+ return error;
80
+ }
81
+ return new Error(String(error));
82
+ }
@@ -1,7 +1,9 @@
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 };
1
+ import { Address, Fetcher, Provider, CepLookupOptions, BulkCepResult, RateLimitOptions, EventName, EventListener, EventMap, ProviderHealth, ProviderMetrics, CircuitBreakerOptions } from "./types";
2
+ import { Cache, InMemoryCache, InMemoryCacheOptions } from "./cache";
3
+ import { CepValidationError, RateLimitError, ProviderTimeoutError, CepNotFoundError, AllProvidersFailedError, ProviderUnavailableError } from "./errors";
4
+ export type { Address, Fetcher, Provider, CepLookupOptions, BulkCepResult, RateLimitOptions, EventName, EventListener, EventMap, Cache, InMemoryCacheOptions, ProviderHealth, ProviderMetrics, CircuitBreakerOptions };
4
5
  export { InMemoryCache };
6
+ export { CepValidationError, RateLimitError, ProviderTimeoutError, CepNotFoundError, AllProvidersFailedError, ProviderUnavailableError };
5
7
  /**
6
8
  * @class CepLookup
7
9
  * @description A class for looking up Brazilian postal codes (CEPs) using multiple providers.
@@ -13,32 +15,35 @@ 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;
23
+ private circuitBreakerEnabled;
24
+ private circuitFailureThreshold;
25
+ private circuitCooldownMs;
26
+ private providerState;
18
27
  constructor(options: CepLookupOptions);
28
+ private log;
19
29
  on<T extends EventName>(eventName: T, listener: EventListener<T>): void;
20
30
  off<T extends EventName>(eventName: T, listener: EventListener<T>): void;
21
31
  /**
22
32
  * @method warmup
23
33
  * @description Pings providers to determine the fastest one and updates the internal priority order.
24
34
  * Useful to call on UI events like 'focus' on the CEP input.
35
+ * @returns {Promise<Provider[]>} The list of providers sorted by latency.
25
36
  */
26
- warmup(): Promise<void>;
37
+ warmup(): Promise<Provider[]>;
38
+ private getOrCreateProviderState;
39
+ private recordProviderSuccess;
40
+ private recordProviderFailure;
41
+ private isProviderOpen;
42
+ private scoreProvider;
43
+ getProviderHealth(): ProviderHealth[];
44
+ getProviderMetrics(): ProviderMetrics[];
27
45
  private checkRateLimit;
28
46
  lookup<T = Address>(cep: string, mapper?: (address: Address) => T): Promise<T>;
29
- lookupCeps(ceps: string[], concurrency?: number): Promise<BulkCepResult[]>;
47
+ private _lookupFromProviders;
48
+ lookupCeps<T = Address>(ceps: string[], concurrency?: number, mapper?: (address: Address) => T): Promise<BulkCepResult<T>[]>;
30
49
  }
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,16 @@
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.ProviderUnavailableError = 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
+ Object.defineProperty(exports, "ProviderUnavailableError", { enumerable: true, get: function () { return errors_1.ProviderUnavailableError; } });
13
+ const ddd_by_state_1 = require("./data/ddd-by-state");
8
14
  // Minimal EventEmitter for internal use
9
15
  class EventEmitter {
10
16
  constructor() {
@@ -41,7 +47,7 @@ class EventEmitter {
41
47
  function validateCep(cep) {
42
48
  const cepRegex = /^(\d{8}|\d{5}-\d{3})$/;
43
49
  if (!cepRegex.test(cep)) {
44
- throw new Error("Invalid CEP format. Use either NNNNNNNN or NNNNN-NNN.");
50
+ throw new errors_1.CepValidationError(cep);
45
51
  }
46
52
  return cep.replace("-", "");
47
53
  }
@@ -61,6 +67,19 @@ function sanitizeAddress(address) {
61
67
  });
62
68
  return sanitized;
63
69
  }
70
+ /**
71
+ * @function enrichAddress
72
+ * @description Enriches an address with DDD fallback when the provider doesn't return it.
73
+ */
74
+ function enrichAddress(address) {
75
+ if (!address.ddd && address.state) {
76
+ const fallbackDdd = ddd_by_state_1.dddByState[address.state];
77
+ if (fallbackDdd) {
78
+ return { ...address, ddd: fallbackDdd };
79
+ }
80
+ }
81
+ return address;
82
+ }
64
83
  /**
65
84
  * @class CepLookup
66
85
  * @description A class for looking up Brazilian postal codes (CEPs) using multiple providers.
@@ -68,6 +87,7 @@ function sanitizeAddress(address) {
68
87
  class CepLookup {
69
88
  constructor(options) {
70
89
  this.requestTimestamps = [];
90
+ this.providerState = new Map();
71
91
  this.providers = options.providers;
72
92
  this.sortedProviders = [...options.providers];
73
93
  this.emitter = new EventEmitter();
@@ -81,6 +101,26 @@ class CepLookup {
81
101
  this.cache = options.cache;
82
102
  this.rateLimit = options.rateLimit;
83
103
  this.staggerDelay = options.staggerDelay ?? 100;
104
+ this.retries = options.retries ?? 0;
105
+ this.retryDelay = options.retryDelay ?? 1000;
106
+ this.logger = options.logger;
107
+ this.circuitBreakerEnabled = options.circuitBreaker?.enabled ?? true;
108
+ this.circuitFailureThreshold = options.circuitBreaker?.failureThreshold ?? 3;
109
+ this.circuitCooldownMs = options.circuitBreaker?.cooldownMs ?? 30000;
110
+ this.providers.forEach((provider) => {
111
+ this.providerState.set(provider.name, {
112
+ consecutiveFailures: 0,
113
+ successCount: 0,
114
+ failureCount: 0,
115
+ avgLatencyMs: 0,
116
+ requests: 0,
117
+ timeoutErrors: 0,
118
+ notFoundErrors: 0,
119
+ });
120
+ });
121
+ }
122
+ log(msg, data) {
123
+ this.logger?.debug(msg, data);
84
124
  }
85
125
  on(eventName, listener) {
86
126
  this.emitter.on(eventName, listener);
@@ -92,6 +132,7 @@ class CepLookup {
92
132
  * @method warmup
93
133
  * @description Pings providers to determine the fastest one and updates the internal priority order.
94
134
  * Useful to call on UI events like 'focus' on the CEP input.
135
+ * @returns {Promise<Provider[]>} The list of providers sorted by latency.
95
136
  */
96
137
  async warmup() {
97
138
  const controlCep = "01001000"; // Praça da Sé (Fixed Valid CEP)
@@ -117,6 +158,101 @@ class CepLookup {
117
158
  .filter(p => !!p);
118
159
  // Abort any lingering requests (though we awaited all)
119
160
  controller.abort();
161
+ return this.sortedProviders;
162
+ }
163
+ getOrCreateProviderState(providerName) {
164
+ const existing = this.providerState.get(providerName);
165
+ if (existing)
166
+ return existing;
167
+ const created = {
168
+ consecutiveFailures: 0,
169
+ successCount: 0,
170
+ failureCount: 0,
171
+ avgLatencyMs: 0,
172
+ requests: 0,
173
+ timeoutErrors: 0,
174
+ notFoundErrors: 0,
175
+ };
176
+ this.providerState.set(providerName, created);
177
+ return created;
178
+ }
179
+ recordProviderSuccess(providerName, durationMs) {
180
+ const state = this.getOrCreateProviderState(providerName);
181
+ state.requests += 1;
182
+ state.successCount += 1;
183
+ state.consecutiveFailures = 0;
184
+ const n = state.successCount + state.failureCount;
185
+ state.avgLatencyMs = n === 1 ? durationMs : ((state.avgLatencyMs * (n - 1)) + durationMs) / n;
186
+ state.openUntil = undefined;
187
+ }
188
+ recordProviderFailure(providerName, durationMs, error) {
189
+ const state = this.getOrCreateProviderState(providerName);
190
+ state.requests += 1;
191
+ state.failureCount += 1;
192
+ state.consecutiveFailures += 1;
193
+ const n = state.successCount + state.failureCount;
194
+ state.avgLatencyMs = n === 1 ? durationMs : ((state.avgLatencyMs * (n - 1)) + durationMs) / n;
195
+ if (error instanceof errors_1.ProviderTimeoutError) {
196
+ state.timeoutErrors += 1;
197
+ }
198
+ if (error instanceof errors_1.CepNotFoundError) {
199
+ state.notFoundErrors += 1;
200
+ }
201
+ if (this.circuitBreakerEnabled && state.consecutiveFailures >= this.circuitFailureThreshold) {
202
+ state.openUntil = Date.now() + this.circuitCooldownMs;
203
+ }
204
+ }
205
+ isProviderOpen(providerName) {
206
+ if (!this.circuitBreakerEnabled)
207
+ return false;
208
+ const state = this.getOrCreateProviderState(providerName);
209
+ if (!state.openUntil)
210
+ return false;
211
+ if (Date.now() >= state.openUntil) {
212
+ state.openUntil = undefined;
213
+ state.consecutiveFailures = 0;
214
+ return false;
215
+ }
216
+ return true;
217
+ }
218
+ scoreProvider(provider) {
219
+ const state = this.getOrCreateProviderState(provider.name);
220
+ const total = state.successCount + state.failureCount;
221
+ const successRate = total === 0 ? 1 : state.successCount / total;
222
+ const latencyPenalty = state.avgLatencyMs > 0 ? Math.min(state.avgLatencyMs / 1000, 1) : 0;
223
+ const openPenalty = this.isProviderOpen(provider.name) ? 1 : 0;
224
+ return (successRate * 0.8) + ((1 - latencyPenalty) * 0.2) - openPenalty;
225
+ }
226
+ getProviderHealth() {
227
+ return this.providers
228
+ .map((provider) => {
229
+ const state = this.getOrCreateProviderState(provider.name);
230
+ return {
231
+ provider: provider.name,
232
+ score: Number(this.scoreProvider(provider).toFixed(4)),
233
+ isOpen: this.isProviderOpen(provider.name),
234
+ openUntil: state.openUntil,
235
+ consecutiveFailures: state.consecutiveFailures,
236
+ successCount: state.successCount,
237
+ failureCount: state.failureCount,
238
+ avgLatencyMs: Number(state.avgLatencyMs.toFixed(2)),
239
+ };
240
+ })
241
+ .sort((a, b) => b.score - a.score);
242
+ }
243
+ getProviderMetrics() {
244
+ return this.providers.map((provider) => {
245
+ const state = this.getOrCreateProviderState(provider.name);
246
+ return {
247
+ provider: provider.name,
248
+ requests: state.requests,
249
+ successes: state.successCount,
250
+ failures: state.failureCount,
251
+ timeoutErrors: state.timeoutErrors,
252
+ notFoundErrors: state.notFoundErrors,
253
+ avgLatencyMs: Number(state.avgLatencyMs.toFixed(2)),
254
+ };
255
+ });
120
256
  }
121
257
  checkRateLimit() {
122
258
  if (!this.rateLimit)
@@ -125,33 +261,67 @@ class CepLookup {
125
261
  const windowStart = now - this.rateLimit.per;
126
262
  this.requestTimestamps = this.requestTimestamps.filter((ts) => ts > windowStart);
127
263
  if (this.requestTimestamps.length >= this.rateLimit.requests) {
128
- throw new Error(`Rate limit exceeded: ${this.rateLimit.requests} requests per ${this.rateLimit.per}ms.`);
264
+ throw new errors_1.RateLimitError(this.rateLimit.requests, this.rateLimit.per);
129
265
  }
130
266
  this.requestTimestamps.push(now);
131
267
  }
132
268
  async lookup(cep, mapper) {
133
269
  this.checkRateLimit();
134
270
  const cleanedCep = validateCep(cep);
271
+ this.log('lookup:start', { cep: cleanedCep });
135
272
  if (this.cache) {
136
273
  const cachedAddress = this.cache.get(cleanedCep);
137
274
  if (cachedAddress) {
275
+ this.log('cache:hit', { cep: cleanedCep });
138
276
  this.emitter.emit('cache:hit', { cep: cleanedCep });
139
277
  return mapper ? mapper(cachedAddress) : cachedAddress;
140
278
  }
141
279
  }
280
+ let lastError;
281
+ const maxAttempts = 1 + this.retries;
282
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
283
+ if (attempt > 0) {
284
+ const delay = this.retryDelay * Math.pow(2, attempt - 1);
285
+ this.log('retry:attempt', { attempt, cep: cleanedCep, delay });
286
+ await new Promise(resolve => setTimeout(resolve, delay));
287
+ }
288
+ try {
289
+ return await this._lookupFromProviders(cleanedCep, mapper);
290
+ }
291
+ catch (error) {
292
+ if (error instanceof errors_1.CepValidationError || error instanceof errors_1.RateLimitError) {
293
+ throw error;
294
+ }
295
+ lastError = error;
296
+ }
297
+ }
298
+ throw lastError;
299
+ }
300
+ async _lookupFromProviders(cleanedCep, mapper) {
142
301
  const controller = new AbortController();
143
302
  const { signal } = controller;
144
- // Helper to create the promise for a specific provider
303
+ const availableProviders = this.sortedProviders.filter((provider) => !this.isProviderOpen(provider.name));
304
+ const providersByHealth = [...availableProviders].sort((a, b) => this.scoreProvider(b) - this.scoreProvider(a));
305
+ const selectedProviders = providersByHealth.length > 0 ? providersByHealth : [...this.sortedProviders].sort((a, b) => this.scoreProvider(b) - this.scoreProvider(a));
306
+ if (selectedProviders.length === 0) {
307
+ throw new errors_1.AllProvidersFailedError([new errors_1.ProviderUnavailableError("all")]);
308
+ }
309
+ if (availableProviders.length === 0 && this.circuitBreakerEnabled) {
310
+ throw new errors_1.AllProvidersFailedError(selectedProviders.map((p) => new errors_1.ProviderUnavailableError(p.name)));
311
+ }
145
312
  const createProviderPromise = (provider) => {
146
313
  const startTime = Date.now();
147
314
  const url = provider.buildUrl(cleanedCep);
315
+ this.log('provider:start', { provider: provider.name, cep: cleanedCep });
148
316
  const timeoutPromise = new Promise((_, reject) => {
149
317
  if (!provider.timeout)
150
318
  return;
151
319
  const timeoutId = setTimeout(() => {
152
320
  signal.removeEventListener('abort', onAbort);
153
321
  const duration = Date.now() - startTime;
154
- const error = new Error(`Timeout from ${provider.name}`);
322
+ const error = new errors_1.ProviderTimeoutError(provider.name, provider.timeout);
323
+ this.recordProviderFailure(provider.name, duration, error);
324
+ this.log('provider:failure', { provider: provider.name, cep: cleanedCep, error: error.message });
155
325
  this.emitter.emit('failure', { provider: provider.name, cep: cleanedCep, duration, error });
156
326
  reject(error);
157
327
  }, provider.timeout);
@@ -162,7 +332,9 @@ class CepLookup {
162
332
  .then((response) => provider.transform(response))
163
333
  .then((address) => {
164
334
  const duration = Date.now() - startTime;
165
- const sanitizedAddress = sanitizeAddress(address);
335
+ const sanitizedAddress = enrichAddress(sanitizeAddress(address));
336
+ this.recordProviderSuccess(provider.name, duration);
337
+ this.log('provider:success', { provider: provider.name, cep: cleanedCep, duration });
166
338
  this.emitter.emit('success', { provider: provider.name, cep: cleanedCep, duration, address: sanitizedAddress });
167
339
  if (this.cache) {
168
340
  this.cache.set(cleanedCep, sanitizedAddress);
@@ -171,18 +343,18 @@ class CepLookup {
171
343
  })
172
344
  .catch((error) => {
173
345
  const duration = Date.now() - startTime;
174
- // Only emit failure if it's not a self-induced abortion or timeout handled elsewhere
175
- if (!error.message.includes('Timeout from') && error.name !== 'AbortError') {
176
- this.emitter.emit('failure', { provider: provider.name, cep: cleanedCep, duration, error });
346
+ const normalizedError = (0, errors_1.normalizeProviderError)(error, cleanedCep, provider.name);
347
+ if (normalizedError.name !== 'AbortError' && !(normalizedError instanceof errors_1.ProviderTimeoutError)) {
348
+ this.recordProviderFailure(provider.name, duration, normalizedError);
349
+ this.log('provider:failure', { provider: provider.name, cep: cleanedCep, error: normalizedError.message });
350
+ this.emitter.emit('failure', { provider: provider.name, cep: cleanedCep, duration, error: normalizedError });
177
351
  }
178
- throw error;
352
+ throw normalizedError;
179
353
  });
180
354
  return Promise.race([fetchPromise, timeoutPromise]);
181
355
  };
182
- // Staggered Strategy using sortedProviders
183
- const bestProvider = this.sortedProviders[0];
184
- const otherProviders = this.sortedProviders.slice(1);
185
- // If we only have one provider, just execute it
356
+ const bestProvider = selectedProviders[0];
357
+ const otherProviders = selectedProviders.slice(1);
186
358
  if (otherProviders.length === 0) {
187
359
  try {
188
360
  return await createProviderPromise(bestProvider);
@@ -191,7 +363,6 @@ class CepLookup {
191
363
  controller.abort();
192
364
  }
193
365
  }
194
- // Execute primary and manage staggering
195
366
  let staggerTimeout = null;
196
367
  let triggerOthers = null;
197
368
  const secondaryPromise = new Promise((resolve, reject) => {
@@ -206,7 +377,6 @@ class CepLookup {
206
377
  staggerTimeout = setTimeout(triggerOthers, this.staggerDelay);
207
378
  });
208
379
  const primaryPromise = createProviderPromise(bestProvider).catch((err) => {
209
- // If primary fails, trigger others immediately
210
380
  if (triggerOthers)
211
381
  triggerOthers();
212
382
  throw err;
@@ -214,13 +384,17 @@ class CepLookup {
214
384
  try {
215
385
  return await Promise.any([primaryPromise, secondaryPromise]);
216
386
  }
387
+ catch (aggregateError) {
388
+ const errors = aggregateError.errors || [aggregateError];
389
+ throw new errors_1.AllProvidersFailedError(errors);
390
+ }
217
391
  finally {
218
392
  if (staggerTimeout)
219
393
  clearTimeout(staggerTimeout);
220
394
  controller.abort();
221
395
  }
222
396
  }
223
- async lookupCeps(ceps, concurrency = 5) {
397
+ async lookupCeps(ceps, concurrency = 5, mapper) {
224
398
  if (!ceps || ceps.length === 0) {
225
399
  return [];
226
400
  }
@@ -235,7 +409,11 @@ class CepLookup {
235
409
  try {
236
410
  const address = await this.lookup(cep);
237
411
  if (address) {
238
- results[currentIndex] = { cep, data: address, provider: address.service };
412
+ results[currentIndex] = {
413
+ cep,
414
+ data: mapper ? mapper(address) : address,
415
+ provider: address.service,
416
+ };
239
417
  }
240
418
  else {
241
419
  throw new Error('No address found');
@@ -252,21 +430,3 @@ class CepLookup {
252
430
  }
253
431
  }
254
432
  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;