iptuapi 1.1.0 → 2.0.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/dist/index.mjs CHANGED
@@ -1,166 +1,513 @@
1
1
  // src/index.ts
2
- var IPTUAPIError = class extends Error {
3
- constructor(message, statusCode) {
2
+ var CidadeEnum = {
3
+ SAO_PAULO: "sp",
4
+ BELO_HORIZONTE: "bh",
5
+ RECIFE: "recife"
6
+ };
7
+ var IPTUAPIError = class _IPTUAPIError extends Error {
8
+ statusCode;
9
+ requestId;
10
+ responseBody;
11
+ constructor(message, statusCode, requestId, responseBody) {
4
12
  super(message);
5
- this.statusCode = statusCode;
6
13
  this.name = "IPTUAPIError";
14
+ this.statusCode = statusCode;
15
+ this.requestId = requestId;
16
+ this.responseBody = responseBody;
17
+ Object.setPrototypeOf(this, _IPTUAPIError.prototype);
18
+ }
19
+ get isRetryable() {
20
+ return this.statusCode ? [429, 500, 502, 503, 504].includes(this.statusCode) : false;
7
21
  }
8
22
  };
9
- var AuthenticationError = class extends IPTUAPIError {
10
- constructor(message = "API Key inv\xE1lida ou expirada") {
11
- super(message, 401);
23
+ var AuthenticationError = class _AuthenticationError extends IPTUAPIError {
24
+ constructor(message = "API Key inv\xE1lida ou expirada", requestId, responseBody) {
25
+ super(message, 401, requestId, responseBody);
12
26
  this.name = "AuthenticationError";
27
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
13
28
  }
14
29
  };
15
- var RateLimitError = class extends IPTUAPIError {
16
- constructor(message = "Limite de requisi\xE7\xF5es excedido") {
17
- super(message, 429);
18
- this.name = "RateLimitError";
30
+ var ForbiddenError = class _ForbiddenError extends IPTUAPIError {
31
+ requiredPlan;
32
+ constructor(message = "Plano n\xE3o autorizado para este recurso", requiredPlan, requestId, responseBody) {
33
+ super(message, 403, requestId, responseBody);
34
+ this.name = "ForbiddenError";
35
+ this.requiredPlan = requiredPlan;
36
+ Object.setPrototypeOf(this, _ForbiddenError.prototype);
19
37
  }
20
38
  };
21
- var NotFoundError = class extends IPTUAPIError {
22
- constructor(message = "Recurso n\xE3o encontrado") {
23
- super(message, 404);
39
+ var NotFoundError = class _NotFoundError extends IPTUAPIError {
40
+ constructor(message = "Recurso n\xE3o encontrado", requestId, responseBody) {
41
+ super(message, 404, requestId, responseBody);
24
42
  this.name = "NotFoundError";
43
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
25
44
  }
26
45
  };
27
- var ForbiddenError = class extends IPTUAPIError {
28
- constructor(message = "Plano n\xE3o autorizado para este recurso") {
29
- super(message, 403);
30
- this.name = "ForbiddenError";
46
+ var RateLimitError = class _RateLimitError extends IPTUAPIError {
47
+ retryAfter;
48
+ limit;
49
+ remaining;
50
+ constructor(message = "Limite de requisi\xE7\xF5es excedido", retryAfter, limit, remaining, requestId, responseBody) {
51
+ super(message, 429, requestId, responseBody);
52
+ this.name = "RateLimitError";
53
+ this.retryAfter = retryAfter;
54
+ this.limit = limit;
55
+ this.remaining = remaining;
56
+ Object.setPrototypeOf(this, _RateLimitError.prototype);
57
+ }
58
+ get isRetryable() {
59
+ return true;
60
+ }
61
+ };
62
+ var ValidationError = class _ValidationError extends IPTUAPIError {
63
+ errors;
64
+ constructor(message = "Par\xE2metros inv\xE1lidos", errors, requestId, responseBody) {
65
+ super(message, 400, requestId, responseBody);
66
+ this.name = "ValidationError";
67
+ this.errors = errors;
68
+ Object.setPrototypeOf(this, _ValidationError.prototype);
31
69
  }
32
70
  };
71
+ var ServerError = class _ServerError extends IPTUAPIError {
72
+ constructor(message = "Erro interno do servidor", statusCode = 500, requestId, responseBody) {
73
+ super(message, statusCode, requestId, responseBody);
74
+ this.name = "ServerError";
75
+ Object.setPrototypeOf(this, _ServerError.prototype);
76
+ }
77
+ get isRetryable() {
78
+ return true;
79
+ }
80
+ };
81
+ var TimeoutError = class _TimeoutError extends IPTUAPIError {
82
+ timeoutMs;
83
+ constructor(message = "Timeout na requisi\xE7\xE3o", timeoutMs) {
84
+ super(message, 408);
85
+ this.name = "TimeoutError";
86
+ this.timeoutMs = timeoutMs;
87
+ Object.setPrototypeOf(this, _TimeoutError.prototype);
88
+ }
89
+ get isRetryable() {
90
+ return true;
91
+ }
92
+ };
93
+ var NetworkError = class _NetworkError extends IPTUAPIError {
94
+ originalError;
95
+ constructor(message = "Erro de conex\xE3o com a API", originalError) {
96
+ super(message);
97
+ this.name = "NetworkError";
98
+ this.originalError = originalError;
99
+ Object.setPrototypeOf(this, _NetworkError.prototype);
100
+ }
101
+ get isRetryable() {
102
+ return true;
103
+ }
104
+ };
105
+ var DEFAULT_RETRY_CONFIG = {
106
+ maxRetries: 3,
107
+ initialDelay: 500,
108
+ maxDelay: 1e4,
109
+ backoffFactor: 2,
110
+ retryableStatuses: [429, 500, 502, 503, 504]
111
+ };
33
112
  var IPTUClient = class {
34
113
  apiKey;
35
114
  baseUrl;
36
115
  timeout;
116
+ retryConfig;
117
+ logger;
118
+ logRequests;
119
+ logResponses;
120
+ userAgent;
121
+ _rateLimit;
122
+ _lastRequestId;
37
123
  constructor(apiKey, options = {}) {
124
+ if (!apiKey) {
125
+ throw new Error("API Key \xE9 obrigat\xF3ria");
126
+ }
38
127
  this.apiKey = apiKey;
39
128
  this.baseUrl = options.baseUrl || "https://iptuapi.com.br/api/v1";
40
129
  this.timeout = options.timeout || 3e4;
130
+ this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...options.retry };
131
+ this.logger = options.logger;
132
+ this.logRequests = options.logRequests || false;
133
+ this.logResponses = options.logResponses || false;
134
+ this.userAgent = options.userAgent || "iptuapi-js/2.0.0";
135
+ }
136
+ // ===========================================================================
137
+ // Properties
138
+ // ===========================================================================
139
+ /** Rate limit info from last request */
140
+ get rateLimit() {
141
+ return this._rateLimit;
142
+ }
143
+ /** Request ID from last request (useful for support) */
144
+ get lastRequestId() {
145
+ return this._lastRequestId;
146
+ }
147
+ // ===========================================================================
148
+ // Private Methods
149
+ // ===========================================================================
150
+ log(level, message, ...args) {
151
+ if (this.logger && this.logger[level]) {
152
+ this.logger[level](message, ...args);
153
+ }
154
+ }
155
+ sleep(ms) {
156
+ return new Promise((resolve) => setTimeout(resolve, ms));
157
+ }
158
+ calculateDelay(attempt) {
159
+ const delay = this.retryConfig.initialDelay * Math.pow(this.retryConfig.backoffFactor, attempt);
160
+ return Math.min(delay, this.retryConfig.maxDelay);
161
+ }
162
+ extractRateLimit(headers) {
163
+ const limit = headers.get("X-RateLimit-Limit");
164
+ const remaining = headers.get("X-RateLimit-Remaining");
165
+ const reset = headers.get("X-RateLimit-Reset");
166
+ if (limit && remaining && reset) {
167
+ const resetTimestamp = parseInt(reset, 10);
168
+ return {
169
+ limit: parseInt(limit, 10),
170
+ remaining: parseInt(remaining, 10),
171
+ reset: resetTimestamp,
172
+ resetDate: new Date(resetTimestamp * 1e3)
173
+ };
174
+ }
175
+ return void 0;
176
+ }
177
+ async handleErrorResponse(response, requestId) {
178
+ let body = {};
179
+ try {
180
+ body = await response.json();
181
+ } catch {
182
+ body = { detail: response.statusText };
183
+ }
184
+ const message = body.detail || `HTTP ${response.status}`;
185
+ switch (response.status) {
186
+ case 400:
187
+ case 422:
188
+ throw new ValidationError(
189
+ message,
190
+ body.errors,
191
+ requestId,
192
+ body
193
+ );
194
+ case 401:
195
+ throw new AuthenticationError(message, requestId, body);
196
+ case 403:
197
+ throw new ForbiddenError(
198
+ message,
199
+ body.required_plan,
200
+ requestId,
201
+ body
202
+ );
203
+ case 404:
204
+ throw new NotFoundError(message, requestId, body);
205
+ case 429:
206
+ const retryAfter = response.headers.get("Retry-After");
207
+ throw new RateLimitError(
208
+ message,
209
+ retryAfter ? parseInt(retryAfter, 10) : void 0,
210
+ this._rateLimit?.limit,
211
+ this._rateLimit?.remaining,
212
+ requestId,
213
+ body
214
+ );
215
+ case 500:
216
+ case 502:
217
+ case 503:
218
+ case 504:
219
+ throw new ServerError(message, response.status, requestId, body);
220
+ default:
221
+ throw new IPTUAPIError(message, response.status, requestId, body);
222
+ }
41
223
  }
42
224
  async request(method, endpoint, params, body) {
43
225
  const url = new URL(`${this.baseUrl}${endpoint}`);
44
226
  if (params) {
45
227
  Object.entries(params).forEach(([key, value]) => {
46
- if (value) url.searchParams.append(key, value);
228
+ if (value !== void 0 && value !== null && value !== "") {
229
+ url.searchParams.append(key, String(value));
230
+ }
47
231
  });
48
232
  }
49
- const controller = new AbortController();
50
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
51
- try {
52
- const response = await fetch(url.toString(), {
53
- method,
54
- headers: {
55
- "X-API-Key": this.apiKey,
56
- "Content-Type": "application/json"
57
- },
58
- body: body ? JSON.stringify(body) : void 0,
59
- signal: controller.signal
60
- });
61
- clearTimeout(timeoutId);
62
- if (response.ok) {
63
- return response.json();
64
- }
65
- switch (response.status) {
66
- case 401:
67
- throw new AuthenticationError();
68
- case 403:
69
- throw new ForbiddenError();
70
- case 404:
71
- throw new NotFoundError();
72
- case 429:
73
- throw new RateLimitError();
74
- default:
75
- const errorData = await response.json().catch(() => ({}));
76
- throw new IPTUAPIError(
77
- errorData.detail || `Erro na API: ${response.statusText}`,
78
- response.status
233
+ const headers = {
234
+ "X-API-Key": this.apiKey,
235
+ "Content-Type": "application/json",
236
+ Accept: "application/json",
237
+ "User-Agent": this.userAgent
238
+ };
239
+ let lastError;
240
+ let attempt = 0;
241
+ while (attempt <= this.retryConfig.maxRetries) {
242
+ const controller = new AbortController();
243
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
244
+ try {
245
+ if (this.logRequests) {
246
+ this.log(
247
+ "debug",
248
+ `Request: ${method} ${url}`,
249
+ params ? { params } : {},
250
+ body ? { body } : {}
79
251
  );
252
+ }
253
+ const startTime = Date.now();
254
+ const response = await fetch(url.toString(), {
255
+ method,
256
+ headers,
257
+ body: body ? JSON.stringify(body) : void 0,
258
+ signal: controller.signal
259
+ });
260
+ clearTimeout(timeoutId);
261
+ const elapsedMs = Date.now() - startTime;
262
+ this._rateLimit = this.extractRateLimit(response.headers);
263
+ this._lastRequestId = response.headers.get("X-Request-ID") || void 0;
264
+ if (this.logResponses) {
265
+ this.log(
266
+ "debug",
267
+ `Response: ${response.status} ${url} (${elapsedMs}ms)`
268
+ );
269
+ }
270
+ if (response.ok) {
271
+ return await response.json();
272
+ }
273
+ if (this.retryConfig.retryableStatuses.includes(response.status) && attempt < this.retryConfig.maxRetries) {
274
+ const delay = this.calculateDelay(attempt);
275
+ this.log(
276
+ "warn",
277
+ `Request failed with ${response.status}, retrying in ${delay}ms (attempt ${attempt + 1}/${this.retryConfig.maxRetries})`
278
+ );
279
+ await this.sleep(delay);
280
+ attempt++;
281
+ continue;
282
+ }
283
+ await this.handleErrorResponse(response, this._lastRequestId);
284
+ } catch (error) {
285
+ clearTimeout(timeoutId);
286
+ if (error instanceof IPTUAPIError) {
287
+ throw error;
288
+ }
289
+ if (error instanceof Error) {
290
+ if (error.name === "AbortError") {
291
+ lastError = new TimeoutError(
292
+ `Timeout ap\xF3s ${this.timeout}ms`,
293
+ this.timeout
294
+ );
295
+ } else if (error.message.includes("fetch") || error.message.includes("network")) {
296
+ lastError = new NetworkError(
297
+ `Erro de conex\xE3o: ${error.message}`,
298
+ error
299
+ );
300
+ } else {
301
+ lastError = error;
302
+ }
303
+ if (attempt < this.retryConfig.maxRetries) {
304
+ const delay = this.calculateDelay(attempt);
305
+ this.log(
306
+ "warn",
307
+ `Request failed: ${error.message}, retrying in ${delay}ms (attempt ${attempt + 1}/${this.retryConfig.maxRetries})`
308
+ );
309
+ await this.sleep(delay);
310
+ attempt++;
311
+ continue;
312
+ }
313
+ }
314
+ throw lastError || error;
80
315
  }
81
- } catch (error) {
82
- clearTimeout(timeoutId);
83
- if (error instanceof IPTUAPIError) throw error;
84
- if (error instanceof Error && error.name === "AbortError") {
85
- throw new IPTUAPIError("Timeout na requisi\xE7\xE3o", 408);
86
- }
87
- throw error;
88
316
  }
317
+ throw lastError || new IPTUAPIError("Max retries exceeded");
318
+ }
319
+ async consultaEndereco(paramsOrLogradouro, numero, cidade) {
320
+ let params;
321
+ if (typeof paramsOrLogradouro === "string") {
322
+ params = {
323
+ logradouro: paramsOrLogradouro,
324
+ numero,
325
+ cidade: cidade || "sp"
326
+ };
327
+ } else {
328
+ params = {
329
+ logradouro: paramsOrLogradouro.logradouro,
330
+ numero: paramsOrLogradouro.numero,
331
+ complemento: paramsOrLogradouro.complemento,
332
+ cidade: paramsOrLogradouro.cidade || "sp",
333
+ incluir_historico: paramsOrLogradouro.incluirHistorico,
334
+ incluir_comparaveis: paramsOrLogradouro.incluirComparaveis,
335
+ incluir_zoneamento: paramsOrLogradouro.incluirZoneamento
336
+ };
337
+ }
338
+ return this.request(
339
+ "GET",
340
+ "/consulta/endereco",
341
+ params
342
+ );
89
343
  }
90
344
  /**
91
- * Busca dados de IPTU por endereço
345
+ * Busca dados de IPTU por número SQL (contribuinte).
346
+ *
347
+ * @param sql - Número SQL do imóvel
348
+ * @param cidade - Cidade da consulta
349
+ * @param options - Opções adicionais
350
+ * @returns Dados completos do imóvel
92
351
  */
93
- async consultaEndereco(logradouro, numero) {
94
- return this.request("GET", "/consulta/endereco", {
95
- logradouro,
96
- numero: numero || ""
352
+ async consultaSQL(sql, cidade = "sp", options) {
353
+ return this.request("GET", `/consulta/sql/${sql}`, {
354
+ cidade,
355
+ incluir_historico: options?.incluirHistorico,
356
+ incluir_comparaveis: options?.incluirComparaveis
97
357
  });
98
358
  }
99
359
  /**
100
- * Busca dados de IPTU por número SQL (Starter+)
360
+ * Busca imóveis por CEP.
361
+ *
362
+ * @param cep - CEP do imóvel
363
+ * @param cidade - Cidade da consulta
364
+ * @returns Lista de imóveis no CEP
101
365
  */
102
- async consultaSQL(sql) {
103
- return this.request("GET", "/consulta/sql", { sql });
366
+ async consultaCEP(cep, cidade = "sp") {
367
+ const cleanCep = cep.replace(/\D/g, "");
368
+ return this.request(
369
+ "GET",
370
+ `/consulta/cep/${cleanCep}`,
371
+ { cidade }
372
+ );
104
373
  }
105
374
  /**
106
- * Busca dados de IPTU por endereço para qualquer cidade suportada
107
- * @param cidade - Cidade ("sao_paulo" ou "belo_horizonte")
108
- * @param logradouro - Nome da rua/avenida
109
- * @param numero - Número do imóvel (opcional)
110
- * @param ano - Ano de referência (default: 2024)
111
- * @param limit - Limite de resultados (default: 20)
375
+ * Consulta zoneamento por coordenadas.
376
+ *
377
+ * @param latitude - Latitude do ponto
378
+ * @param longitude - Longitude do ponto
379
+ * @returns Dados de zoneamento
112
380
  */
113
- async consultaIPTU(cidade, logradouro, numero, ano = 2024, limit = 20) {
114
- const params = {
115
- logradouro,
116
- ano: ano.toString(),
117
- limit: limit.toString()
118
- };
119
- if (numero !== void 0) {
120
- params.numero = numero.toString();
121
- }
381
+ async consultaZoneamento(latitude, longitude) {
382
+ return this.request("GET", "/consulta/zoneamento", {
383
+ latitude,
384
+ longitude
385
+ });
386
+ }
387
+ // ===========================================================================
388
+ // Valuation Endpoints (Pro+)
389
+ // ===========================================================================
390
+ /**
391
+ * Estima o valor de mercado do imóvel usando ML.
392
+ * Disponível apenas para planos Pro e Enterprise.
393
+ *
394
+ * @param params - Parâmetros do imóvel
395
+ * @returns Estimativa de valor
396
+ * @throws {ForbiddenError} Se o plano não permitir
397
+ */
398
+ async valuationEstimate(params) {
399
+ return this.request(
400
+ "POST",
401
+ "/valuation/estimate",
402
+ void 0,
403
+ {
404
+ area_terreno: params.area_terreno,
405
+ area_construida: params.area_construida,
406
+ bairro: params.bairro,
407
+ zona: params.zona,
408
+ tipo_uso: params.tipo_uso,
409
+ tipo_padrao: params.tipo_padrao,
410
+ ano_construcao: params.ano_construcao,
411
+ cidade: params.cidade || "sp"
412
+ }
413
+ );
414
+ }
415
+ /**
416
+ * Valuation em lote (até 100 imóveis).
417
+ * Disponível apenas para plano Enterprise.
418
+ *
419
+ * @param imoveis - Lista de imóveis para avaliar
420
+ * @returns Resultados de valuation para cada imóvel
421
+ */
422
+ async valuationBatch(imoveis) {
423
+ return this.request(
424
+ "POST",
425
+ "/valuation/estimate/batch",
426
+ void 0,
427
+ { imoveis }
428
+ );
429
+ }
430
+ /**
431
+ * Busca imóveis comparáveis para análise.
432
+ *
433
+ * @param bairro - Nome do bairro
434
+ * @param areaMin - Área mínima em m²
435
+ * @param areaMax - Área máxima em m²
436
+ * @param options - Opções adicionais
437
+ * @returns Lista de imóveis comparáveis
438
+ */
439
+ async valuationComparables(bairro, areaMin, areaMax, options) {
440
+ return this.request("GET", "/valuation/comparables", {
441
+ bairro,
442
+ area_min: areaMin,
443
+ area_max: areaMax,
444
+ tipo_uso: options?.tipoUso,
445
+ cidade: options?.cidade || "sp",
446
+ limit: options?.limit || 10
447
+ });
448
+ }
449
+ // ===========================================================================
450
+ // Dados Endpoints
451
+ // ===========================================================================
452
+ /**
453
+ * Histórico de valores IPTU de um imóvel.
454
+ *
455
+ * @param sql - Número SQL do imóvel
456
+ * @param cidade - Cidade da consulta
457
+ * @returns Lista com histórico anual
458
+ */
459
+ async dadosIPTUHistorico(sql, cidade = "sp") {
122
460
  return this.request(
123
461
  "GET",
124
- `/dados/iptu/${cidade}/endereco`,
125
- params
462
+ `/dados/iptu/historico/${sql}`,
463
+ { cidade }
126
464
  );
127
465
  }
128
466
  /**
129
- * Busca dados de IPTU pelo identificador único do imóvel
130
- * @param cidade - Cidade ("sao_paulo" ou "belo_horizonte")
131
- * @param identificador - Número SQL (SP) ou Índice Cadastral (BH)
132
- * @param ano - Ano de referência (opcional)
467
+ * Consulta dados de empresa por CNPJ.
468
+ *
469
+ * @param cnpj - CNPJ da empresa
470
+ * @returns Dados cadastrais
133
471
  */
134
- async consultaIPTUSQL(cidade, identificador, ano) {
135
- const params = {};
136
- if (ano !== void 0) {
137
- params.ano = ano.toString();
138
- }
472
+ async dadosCNPJ(cnpj) {
473
+ const cleanCnpj = cnpj.replace(/\D/g, "");
139
474
  return this.request(
140
475
  "GET",
141
- `/dados/iptu/${cidade}/sql/${encodeURIComponent(identificador)}`,
142
- params
476
+ `/dados/cnpj/${cleanCnpj}`
143
477
  );
144
478
  }
145
479
  /**
146
- * Estima o valor de mercado do imóvel (Pro+)
480
+ * Correção monetária pelo IPCA.
481
+ *
482
+ * @param valor - Valor a corrigir
483
+ * @param dataOrigem - Data do valor original (YYYY-MM)
484
+ * @param dataDestino - Data destino (default: atual)
485
+ * @returns Valor corrigido e fator de correção
147
486
  */
148
- async valuationEstimate(params) {
487
+ async dadosIPCACorrigir(valor, dataOrigem, dataDestino) {
149
488
  return this.request(
150
- "POST",
151
- "/valuation/estimate",
152
- void 0,
153
- params
489
+ "GET",
490
+ "/dados/ipca/corrigir",
491
+ {
492
+ valor,
493
+ data_origem: dataOrigem,
494
+ data_destino: dataDestino
495
+ }
154
496
  );
155
497
  }
156
498
  };
157
499
  var index_default = IPTUClient;
158
500
  export {
159
501
  AuthenticationError,
502
+ CidadeEnum,
160
503
  ForbiddenError,
161
504
  IPTUAPIError,
162
505
  IPTUClient,
506
+ NetworkError,
163
507
  NotFoundError,
164
508
  RateLimitError,
509
+ ServerError,
510
+ TimeoutError,
511
+ ValidationError,
165
512
  index_default as default
166
513
  };
package/package.json CHANGED
@@ -1,15 +1,27 @@
1
1
  {
2
2
  "name": "iptuapi",
3
- "version": "1.1.0",
4
- "description": "SDK oficial para a IPTU API - Dados de IPTU de São Paulo e Belo Horizonte",
3
+ "version": "2.0.0",
4
+ "description": "SDK oficial para a IPTU API - Dados de IPTU de São Paulo, Belo Horizonte e Recife",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
8
15
  "files": [
9
16
  "dist"
10
17
  ],
11
18
  "scripts": {
12
19
  "build": "tsup src/index.ts --format cjs,esm --dts",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "test:coverage": "vitest run --coverage",
23
+ "typecheck": "tsc --noEmit",
24
+ "lint": "eslint src tests",
13
25
  "prepublishOnly": "npm run build"
14
26
  },
15
27
  "keywords": [
@@ -17,9 +29,13 @@
17
29
  "api",
18
30
  "são paulo",
19
31
  "belo horizonte",
32
+ "recife",
20
33
  "imóveis",
21
34
  "dados",
22
- "typescript"
35
+ "real estate",
36
+ "property",
37
+ "typescript",
38
+ "sdk"
23
39
  ],
24
40
  "author": "IPTU API <contato@iptuapi.com.br>",
25
41
  "license": "MIT",
@@ -28,9 +44,16 @@
28
44
  "url": "https://github.com/iptuapi/iptuapi-js"
29
45
  },
30
46
  "homepage": "https://iptuapi.com.br",
47
+ "bugs": {
48
+ "url": "https://github.com/iptuapi/iptuapi-js/issues"
49
+ },
31
50
  "devDependencies": {
51
+ "@types/node": "^20.10.0",
52
+ "@vitest/coverage-v8": "^1.0.0",
53
+ "msw": "^2.0.0",
32
54
  "tsup": "^8.0.0",
33
- "typescript": "^5.3.0"
55
+ "typescript": "^5.3.0",
56
+ "vitest": "^1.0.0"
34
57
  },
35
58
  "engines": {
36
59
  "node": ">=18"