kommo-aiatende-api 1.2.2 → 1.2.3

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
@@ -41,12 +41,30 @@ var AuthManager = class {
41
41
  // src/resources/leads/index.ts
42
42
  import qs from "qs";
43
43
 
44
+ // src/utils/idempotency.ts
45
+ import { randomUUID } from "crypto";
46
+ function ensureRequestId(item) {
47
+ if (item.request_id) return item;
48
+ return { ...item, request_id: randomUUID() };
49
+ }
50
+
44
51
  // src/base.ts
45
52
  var BaseResource = class {
46
53
  client;
47
54
  constructor(client) {
48
55
  this.client = client;
49
56
  }
57
+ /**
58
+ * POST de criação em lote marcado como seguro para retry: cada item recebe um
59
+ * request_id (se ainda não tiver um) para que a Kommo deduplique reenvios após
60
+ * falhas transitórias de rede, e a requisição fica elegível ao retry automático.
61
+ */
62
+ async postIdempotent(url, items) {
63
+ const payload = items.map(ensureRequestId);
64
+ const config = { _idempotentRetry: true };
65
+ const response = await this.client.post(url, payload, config);
66
+ return response.data;
67
+ }
50
68
  };
51
69
 
52
70
  // src/resources/leads/index.ts
@@ -118,8 +136,7 @@ var LeadsResource = class extends BaseResource {
118
136
  }
119
137
  async create(items) {
120
138
  const payload = Array.isArray(items) ? items : [items];
121
- const response = await this.client.post("/api/v4/leads", payload);
122
- return response.data;
139
+ return this.postIdempotent("/api/v4/leads", payload);
123
140
  }
124
141
  async update(items) {
125
142
  const payload = Array.isArray(items) ? items : [items];
@@ -132,8 +149,7 @@ var LeadsResource = class extends BaseResource {
132
149
  }
133
150
  async createComplex(items) {
134
151
  const payload = Array.isArray(items) ? items : [items];
135
- const response = await this.client.post("/api/v4/leads/complex", payload);
136
- return response.data;
152
+ return this.postIdempotent("/api/v4/leads/complex", payload);
137
153
  }
138
154
  async listLossReasons() {
139
155
  const response = await this.client.get("/api/v4/leads/loss_reasons");
@@ -210,8 +226,7 @@ var ContactsResource = class extends BaseResource {
210
226
  }
211
227
  async create(items) {
212
228
  const payload = Array.isArray(items) ? items : [items];
213
- const response = await this.client.post("/api/v4/contacts", payload);
214
- return response.data;
229
+ return this.postIdempotent("/api/v4/contacts", payload);
215
230
  }
216
231
  async update(items) {
217
232
  const payload = Array.isArray(items) ? items : [items];
@@ -678,8 +693,7 @@ var CompaniesResource = class extends BaseResource {
678
693
  }
679
694
  async create(items) {
680
695
  const payload = Array.isArray(items) ? items : [items];
681
- const response = await this.client.post("/api/v4/companies", payload);
682
- return response.data;
696
+ return this.postIdempotent("/api/v4/companies", payload);
683
697
  }
684
698
  async update(items) {
685
699
  const payload = Array.isArray(items) ? items : [items];
@@ -1394,6 +1408,47 @@ var KommoApiError = class extends Error {
1394
1408
  }
1395
1409
  };
1396
1410
 
1411
+ // src/http/retry.ts
1412
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["get", "delete", "patch", "put", "head", "options"]);
1413
+ var TRANSIENT_ERROR_CODES = /* @__PURE__ */ new Set([
1414
+ "ECONNRESET",
1415
+ "ECONNABORTED",
1416
+ "ETIMEDOUT",
1417
+ "EAI_AGAIN",
1418
+ "EPIPE"
1419
+ ]);
1420
+ function isTransientNetworkError(error) {
1421
+ if (error.response) return false;
1422
+ if (error.code && TRANSIENT_ERROR_CODES.has(error.code)) return true;
1423
+ return /socket hang up|network error/i.test(error.message || "");
1424
+ }
1425
+ function isRetryableRequest(config) {
1426
+ const method = (config.method || "get").toLowerCase();
1427
+ if (IDEMPOTENT_METHODS.has(method)) return true;
1428
+ return method === "post" && config._idempotentRetry === true;
1429
+ }
1430
+ function wait(ms) {
1431
+ return new Promise((resolve) => setTimeout(resolve, ms));
1432
+ }
1433
+ function attachRetryInterceptor(client, retryConfig = {}) {
1434
+ const { retries = 2, baseDelayMs = 300, maxDelayMs = 3e3 } = retryConfig;
1435
+ client.interceptors.response.use(void 0, async (error) => {
1436
+ const config = error.config;
1437
+ if (!config || retries <= 0 || !isTransientNetworkError(error) || !isRetryableRequest(config)) {
1438
+ throw error;
1439
+ }
1440
+ const attempt = config._retryCount ?? 0;
1441
+ if (attempt >= retries) {
1442
+ throw error;
1443
+ }
1444
+ config._retryCount = attempt + 1;
1445
+ const backoff = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt);
1446
+ const jitter = backoff * 0.3 * Math.random();
1447
+ await wait(backoff + jitter);
1448
+ return client.request(config);
1449
+ });
1450
+ }
1451
+
1397
1452
  // src/client.ts
1398
1453
  var KommoClient = class {
1399
1454
  config;
@@ -1433,6 +1488,7 @@ var KommoClient = class {
1433
1488
  const baseURL = `https://${config.domain}.kommo.com`;
1434
1489
  this.httpClient = axios2.create({
1435
1490
  baseURL,
1491
+ timeout: config.timeout ?? 3e4,
1436
1492
  headers: {
1437
1493
  "Content-Type": "application/json"
1438
1494
  },
@@ -1440,6 +1496,9 @@ var KommoClient = class {
1440
1496
  return qs19.stringify(params, { arrayFormat: "brackets" });
1441
1497
  }
1442
1498
  });
1499
+ if (config.retry !== false) {
1500
+ attachRetryInterceptor(this.httpClient, config.retry || {});
1501
+ }
1443
1502
  this.httpClient.interceptors.response.use(
1444
1503
  (response) => response,
1445
1504
  (error) => {