hobots 1.6.0 → 1.7.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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/config.ts
2
2
  var DEFAULT_BASE_URL = "https://api2.hobots.app";
3
- var SECRET_PATTERN = /^(hb_)?[a-f0-9]{64}$/i;
3
+ var SECRET_PATTERN = /^hb_[a-f0-9]{64}$/i;
4
4
  function envBaseUrl() {
5
5
  if (typeof process === "undefined" || !process.env) return void 0;
6
6
  return process.env.HOBOTS_API_URL?.trim() || void 0;
@@ -45,7 +45,7 @@ function resolveConfig(options, prefix = "[hobots]") {
45
45
  key,
46
46
  baseUrl,
47
47
  ingestEndpoint: `${baseUrl}/v1/ingest`,
48
- managerBaseUrl: `${baseUrl}/v1/manager`
48
+ tasksBaseUrl: `${baseUrl}/v1/tasks`
49
49
  };
50
50
  }
51
51
 
@@ -103,7 +103,7 @@ function scrubSecrets(text) {
103
103
 
104
104
  // src/utils.ts
105
105
  var SDK_NAME = "hobots";
106
- var SDK_VERSION = true ? "1.6.0" : "0.0.0-dev";
106
+ var SDK_VERSION = true ? "2.0.0" : "0.0.0-dev";
107
107
  function isNode() {
108
108
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
109
109
  }
@@ -139,6 +139,33 @@ function sleep(ms) {
139
139
  function jitter(baseMs) {
140
140
  return Math.floor(baseMs / 2 + Math.random() * (baseMs / 2));
141
141
  }
142
+ function truncate(value, max) {
143
+ return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
144
+ }
145
+ var DEFAULT_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
146
+ async function fetchWithRetry(send, options) {
147
+ const delays = options.delays ?? DEFAULT_RETRY_DELAYS_MS;
148
+ for (let attempt = 0; ; attempt++) {
149
+ let retriable;
150
+ let serverDelay;
151
+ try {
152
+ const response = await send();
153
+ if (response.ok) return response;
154
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) return response;
155
+ if (response.status === 429) serverDelay = retryAfterMs(response);
156
+ retriable = new Error(`HTTP ${response.status}`);
157
+ } catch (error) {
158
+ retriable = error;
159
+ }
160
+ const base = delays[attempt];
161
+ if (base === void 0) {
162
+ throw retriable instanceof Error ? retriable : new Error(String(retriable));
163
+ }
164
+ const delay = serverDelay ?? jitter(base);
165
+ options.logger.warn(`${options.label}: falha (${String(retriable)}), retry em ${delay}ms`);
166
+ await sleep(delay);
167
+ }
168
+ }
142
169
  var MAX_RETRY_AFTER_MS = 6e4;
143
170
  function retryAfterMs(response) {
144
171
  const header = response.headers.get("retry-after");
@@ -239,7 +266,6 @@ function eventFromMessage(message, level = "info") {
239
266
  }
240
267
 
241
268
  // src/transport.ts
242
- var RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
243
269
  var REQUEST_TIMEOUT_MS = 1e4;
244
270
  var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
245
271
  var DEFAULT_MAX_BATCH_SIZE = 10;
@@ -306,36 +332,26 @@ var Transport = class {
306
332
  return request;
307
333
  }
308
334
  async sendWithRetry(envelope) {
309
- for (let attempt = 0; ; attempt++) {
310
- let serverDelay;
311
- try {
312
- const response = await fetch(this.endpoint, {
335
+ let response;
336
+ try {
337
+ response = await fetchWithRetry(
338
+ () => fetch(this.endpoint, {
313
339
  method: "POST",
314
340
  headers: { "content-type": "application/json", authorization: `Bearer ${this.key}` },
315
341
  body: JSON.stringify(envelope),
316
342
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
317
- });
318
- if (response.ok) {
319
- this.logger.debug(`envelope enviado (${envelope.items.length} itens)`);
320
- return;
321
- }
322
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
323
- this.logger.error(`envelope rejeitado (HTTP ${response.status}), descartado`);
324
- return;
325
- }
326
- if (response.status === 429) serverDelay = retryAfterMs(response);
327
- throw new Error(`HTTP ${response.status}`);
328
- } catch (error) {
329
- const base = RETRY_DELAYS_MS[attempt];
330
- if (base === void 0) {
331
- this.logger.error("envelope descartado ap\xF3s retries:", error);
332
- return;
333
- }
334
- const delay = serverDelay ?? jitter(base);
335
- this.logger.warn(`falha no envio (${String(error)}), retry em ${delay}ms`);
336
- await sleep(delay);
337
- }
343
+ }),
344
+ { logger: this.logger, label: "envio de envelope" }
345
+ );
346
+ } catch (error) {
347
+ this.logger.error("envelope descartado ap\xF3s retries:", error);
348
+ return;
338
349
  }
350
+ if (response.ok) {
351
+ this.logger.debug(`envelope enviado (${envelope.items.length} itens)`);
352
+ return;
353
+ }
354
+ this.logger.error(`envelope rejeitado (HTTP ${response.status}), descartado`);
339
355
  }
340
356
  /** Envia o que estiver na fila e aguarda envios em andamento (até timeoutMs). */
341
357
  async flush(timeoutMs = 5e3) {
@@ -368,10 +384,10 @@ var Client = class {
368
384
  #options;
369
385
  #config;
370
386
  transport;
371
- constructor(options) {
387
+ constructor(options, config = resolveConfig(options)) {
372
388
  this.#options = options;
373
389
  this.logger = makeLogger(!!options.debug);
374
- this.#config = resolveConfig(options);
390
+ this.#config = config;
375
391
  this.transport = new Transport({
376
392
  endpoint: this.#config.ingestEndpoint,
377
393
  key: this.#config.key,
@@ -777,50 +793,31 @@ function sendCheckIn(client2, options) {
777
793
  return checkInId;
778
794
  }
779
795
  async function withMonitorImpl(client2, captureError, monitorSlug, fn, options) {
780
- const checkInId = sendCheckIn(client2, {
781
- monitorSlug,
782
- status: "in_progress",
783
- schedule: options?.schedule,
784
- timezone: options?.timezone
785
- });
796
+ const base = { monitorSlug, schedule: options?.schedule, timezone: options?.timezone };
797
+ const checkInId = sendCheckIn(client2, { ...base, status: "in_progress" });
786
798
  const startedAt = Date.now();
787
799
  try {
788
800
  const result = await fn();
789
- sendCheckIn(client2, {
790
- monitorSlug,
791
- status: "ok",
792
- checkInId,
793
- durationMs: Date.now() - startedAt,
794
- schedule: options?.schedule,
795
- timezone: options?.timezone
796
- });
801
+ sendCheckIn(client2, { ...base, status: "ok", checkInId, durationMs: Date.now() - startedAt });
797
802
  return result;
798
803
  } catch (error) {
799
- sendCheckIn(client2, {
800
- monitorSlug,
801
- status: "error",
802
- checkInId,
803
- durationMs: Date.now() - startedAt,
804
- schedule: options?.schedule,
805
- timezone: options?.timezone
806
- });
804
+ sendCheckIn(client2, { ...base, status: "error", checkInId, durationMs: Date.now() - startedAt });
807
805
  captureError(error);
808
806
  throw error;
809
807
  }
810
808
  }
811
809
 
812
- // src/manager/api.ts
813
- var RETRY_DELAYS_MS2 = [1e3, 4e3, 16e3];
810
+ // src/tasks/api.ts
814
811
  var POLL_TIMEOUT_MS = 3e4;
815
812
  var UPDATE_TIMEOUT_MS = 1e4;
816
- var ManagerApiError = class extends Error {
813
+ var TasksApiError = class extends Error {
817
814
  constructor(message, status) {
818
815
  super(message);
819
816
  this.status = status;
820
- this.name = "ManagerApiError";
817
+ this.name = "TasksApiError";
821
818
  }
822
819
  };
823
- var ManagerApi = class {
820
+ var TasksApi = class {
824
821
  baseUrl;
825
822
  key;
826
823
  logger;
@@ -850,7 +847,7 @@ var ManagerApi = class {
850
847
  cleanup();
851
848
  }
852
849
  if (!response.ok) {
853
- throw new ManagerApiError(`poll falhou (HTTP ${response.status})`, response.status);
850
+ throw new TasksApiError(`poll falhou (HTTP ${response.status})`, response.status);
854
851
  }
855
852
  return this.parsePollResponse(await response.json(), request.max ?? 1);
856
853
  }
@@ -858,7 +855,7 @@ var ManagerApi = class {
858
855
  parsePollResponse(body, max) {
859
856
  const runs = body?.runs;
860
857
  if (!Array.isArray(runs)) {
861
- throw new ManagerApiError("resposta inv\xE1lida do poll (campo runs ausente)", 0);
858
+ throw new TasksApiError("resposta inv\xE1lida do poll (campo runs ausente)", 0);
862
859
  }
863
860
  const valid = [];
864
861
  for (const run of runs.slice(0, Math.max(0, max))) {
@@ -873,36 +870,25 @@ var ManagerApi = class {
873
870
  }
874
871
  /**
875
872
  * PATCH de status/logs com retry em falha de rede/5xx/429.
876
- * 4xx é definitivo e vira ManagerApiError imediato (409 = run cancelada/transição inválida).
873
+ * 4xx é definitivo e vira TasksApiError imediato (409 = run cancelada/transição inválida).
877
874
  */
878
875
  async updateRun(runId, update) {
879
- for (let attempt = 0; ; attempt++) {
880
- let retriable;
881
- let serverDelay;
882
- try {
883
- const response = await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
876
+ let response;
877
+ try {
878
+ response = await fetchWithRetry(
879
+ () => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
884
880
  method: "PATCH",
885
881
  headers: this.headers(),
886
882
  body: JSON.stringify(update),
887
883
  signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
888
- });
889
- if (response.ok) return;
890
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
891
- throw new ManagerApiError(`update rejeitado (HTTP ${response.status})`, response.status);
892
- }
893
- if (response.status === 429) serverDelay = retryAfterMs(response);
894
- retriable = new Error(`HTTP ${response.status}`);
895
- } catch (error) {
896
- if (error instanceof ManagerApiError) throw error;
897
- retriable = error;
898
- }
899
- const base = RETRY_DELAYS_MS2[attempt];
900
- if (base === void 0) {
901
- throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(retriable)}`);
902
- }
903
- const delay = serverDelay ?? jitter(base);
904
- this.logger.warn(`falha no update da run ${runId} (${String(retriable)}), retry em ${delay}ms`);
905
- await sleep(delay);
884
+ }),
885
+ { logger: this.logger, label: `update da run ${runId}` }
886
+ );
887
+ } catch (error) {
888
+ throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(error)}`);
889
+ }
890
+ if (!response.ok) {
891
+ throw new TasksApiError(`update rejeitado (HTTP ${response.status})`, response.status);
906
892
  }
907
893
  }
908
894
  /**
@@ -920,7 +906,7 @@ var ManagerApi = class {
920
906
  }
921
907
  };
922
908
 
923
- // src/manager/context.ts
909
+ // src/tasks/context.ts
924
910
  var LOG_LEVEL_COLOR = {
925
911
  debug: "gray",
926
912
  info: "cyan",
@@ -1031,8 +1017,7 @@ var RunReporter = class {
1031
1017
  return;
1032
1018
  }
1033
1019
  this.loggedCount++;
1034
- const truncated = scrubbed.length > MAX_LOG_MESSAGE_CHARS ? `${scrubbed.slice(0, MAX_LOG_MESSAGE_CHARS)}\u2026 [truncado]` : scrubbed;
1035
- this.buffer.push({ ts: nowISO(), level, message: truncated });
1020
+ this.buffer.push({ ts: nowISO(), level, message: truncate(scrubbed, MAX_LOG_MESSAGE_CHARS) });
1036
1021
  if (this.buffer.length >= FLUSH_AT_BUFFERED) {
1037
1022
  this.flushLogs();
1038
1023
  } else {
@@ -1123,7 +1108,7 @@ var RunReporter = class {
1123
1108
  try {
1124
1109
  await this.api.updateRun(this.runId, payload);
1125
1110
  } catch (error) {
1126
- if (error instanceof ManagerApiError && (error.status === 409 || error.status === 404)) {
1111
+ if (error instanceof TasksApiError && (error.status === 409 || error.status === 404)) {
1127
1112
  this.dead = true;
1128
1113
  this.stopCancelWatch();
1129
1114
  this.abort();
@@ -1139,7 +1124,7 @@ var RunReporter = class {
1139
1124
  }
1140
1125
  };
1141
1126
 
1142
- // src/manager/run-context-store.ts
1127
+ // src/tasks/run-context-store.ts
1143
1128
  var storage;
1144
1129
  var initPromise;
1145
1130
  async function ensureStorage() {
@@ -1149,15 +1134,15 @@ async function ensureStorage() {
1149
1134
  });
1150
1135
  await initPromise;
1151
1136
  }
1152
- async function runWithRunId(runId, fn) {
1137
+ async function runWithRunContext(store, fn) {
1153
1138
  await ensureStorage();
1154
- return storage ? storage.run({ runId }, fn) : fn();
1139
+ return storage ? storage.run(store, fn) : fn();
1155
1140
  }
1156
- function currentRunId() {
1157
- return storage?.getStore()?.runId;
1141
+ function currentRunTransaction() {
1142
+ return storage?.getStore()?.tx;
1158
1143
  }
1159
1144
 
1160
- // src/manager/shutdown.ts
1145
+ // src/tasks/shutdown.ts
1161
1146
  var WATCHED_SIGNALS = ["SIGINT", "SIGTERM"];
1162
1147
  function installShutdownHandlers(agent2) {
1163
1148
  if (!isNode()) return () => {
@@ -1201,7 +1186,7 @@ function installShutdownHandlers(agent2) {
1201
1186
  };
1202
1187
  }
1203
1188
 
1204
- // src/manager/agent.ts
1189
+ // src/tasks/agent.ts
1205
1190
  var DEFAULT_POLL_INTERVAL_MS = 5e3;
1206
1191
  var MIN_POLL_INTERVAL_MS = 1e3;
1207
1192
  var MAX_CONCURRENCY = 5;
@@ -1211,13 +1196,13 @@ function hostname() {
1211
1196
  if (!isNode()) return void 0;
1212
1197
  return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
1213
1198
  }
1214
- var ManagerAgent = class {
1199
+ var TaskAgent = class {
1215
1200
  constructor(options, registry2) {
1216
1201
  this.options = options;
1217
1202
  this.registry = registry2;
1218
- this.logger = makeLogger(options.debug ?? false, "[hobots]");
1219
- const config = resolveConfig(options, "[hobots]");
1220
- this.api = new ManagerApi({ baseUrl: config.managerBaseUrl, key: config.key, logger: this.logger });
1203
+ this.logger = makeLogger(options.debug ?? false);
1204
+ const config = options.config;
1205
+ this.api = new TasksApi({ baseUrl: config.tasksBaseUrl, key: config.key, logger: this.logger });
1221
1206
  this.client = options.client;
1222
1207
  this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
1223
1208
  this.concurrency = Math.min(MAX_CONCURRENCY, Math.max(1, options.concurrency ?? 1));
@@ -1225,7 +1210,7 @@ var ManagerAgent = class {
1225
1210
  this.taskTimeoutMs = options.taskTimeoutMs;
1226
1211
  this.logFlushIntervalMs = options.logFlushIntervalMs;
1227
1212
  this.cancelCheckIntervalMs = options.cancelCheckIntervalMs;
1228
- this.logger.debug("agente configurado", { baseUrl: config.managerBaseUrl });
1213
+ this.logger.debug("agente configurado", { baseUrl: config.tasksBaseUrl });
1229
1214
  }
1230
1215
  logger;
1231
1216
  api;
@@ -1347,7 +1332,7 @@ var ManagerAgent = class {
1347
1332
  }
1348
1333
  }
1349
1334
  delayAfterPollError(error) {
1350
- if (error instanceof ManagerApiError && error.status === 401) {
1335
+ if (error instanceof TasksApiError && error.status === 401) {
1351
1336
  this.consecutiveAuthFailures++;
1352
1337
  if (this.consecutiveAuthFailures === 1 || this.consecutiveAuthFailures % 10 === 0) {
1353
1338
  this.logger.error("chave rejeitada (401) \u2014 verifique o Client Secret do projeto em Configura\xE7\xF5es \u2192 Projetos");
@@ -1392,7 +1377,7 @@ var ManagerAgent = class {
1392
1377
  }
1393
1378
  this.logger.debug(`executando run ${run.id} (${run.task})`);
1394
1379
  try {
1395
- await runWithRunId(run.id, () => this.invokeWithTimeout(handler, run, reporter));
1380
+ await runWithRunContext({ runId: run.id, tx }, () => this.invokeWithTimeout(handler, run, reporter));
1396
1381
  reporter.reportSuccess();
1397
1382
  tx.setStatus("ok");
1398
1383
  this.logger.debug(`run ${run.id} conclu\xEDda`);
@@ -1427,9 +1412,6 @@ var ManagerAgent = class {
1427
1412
  var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
1428
1413
  var MAX_ERROR_MESSAGE_CHARS = 1e4;
1429
1414
  var MAX_ERROR_STACK_CHARS = 2e4;
1430
- function truncate(value, max) {
1431
- return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
1432
- }
1433
1415
  function toRunError(error) {
1434
1416
  if (error instanceof Error) {
1435
1417
  return {
@@ -1448,7 +1430,7 @@ var scope = new Scope();
1448
1430
  var heartbeat;
1449
1431
  var agent;
1450
1432
  var registry = /* @__PURE__ */ new Map();
1451
- var warnedManagerDisabled = false;
1433
+ var warnedTasksDisabled = false;
1452
1434
  function init(options) {
1453
1435
  if (client) {
1454
1436
  client.logger.warn("init() chamado mais de uma vez \u2014 substituindo o client anterior");
@@ -1459,8 +1441,9 @@ function init(options) {
1459
1441
  void agent.stop(0);
1460
1442
  agent = void 0;
1461
1443
  }
1462
- warnedManagerDisabled = false;
1463
- client = new Client(options);
1444
+ warnedTasksDisabled = false;
1445
+ const config = resolveConfig(options);
1446
+ client = new Client(options, config);
1464
1447
  if (options.captureUnhandled !== false) {
1465
1448
  installGlobalHandlers(client, () => scope);
1466
1449
  }
@@ -1469,10 +1452,10 @@ function init(options) {
1469
1452
  heartbeat = new Heartbeat(client, heartbeatOptions, options.instanceId);
1470
1453
  heartbeat.start();
1471
1454
  }
1472
- if (options.manager) {
1473
- const managerOptions = options.manager === true ? {} : options.manager;
1474
- agent = new ManagerAgent(
1475
- { ...managerOptions, clientSecret: options.clientSecret, instanceId: options.instanceId, debug: options.debug, client },
1455
+ if (options.tasks) {
1456
+ const tasksOptions = options.tasks === true ? {} : options.tasks;
1457
+ agent = new TaskAgent(
1458
+ { ...tasksOptions, config, instanceId: options.instanceId, debug: options.debug, client },
1476
1459
  registry
1477
1460
  );
1478
1461
  }
@@ -1515,9 +1498,14 @@ function withScope(callback) {
1515
1498
  }
1516
1499
  }
1517
1500
  function startTransaction(name, op) {
1501
+ const active = currentRunTransaction();
1502
+ if (active) {
1503
+ client?.logger.debug(`startTransaction('${name}') dentro de uma execu\xE7\xE3o \u2014 reutilizando a transaction da run`);
1504
+ return active;
1505
+ }
1518
1506
  const rate = client?.options.tracesSampleRate ?? 1;
1519
1507
  const sampled = rate >= 1 || Math.random() < rate;
1520
- return new Transaction(client, name, op, sampled, currentRunId());
1508
+ return new Transaction(client, name, op, sampled);
1521
1509
  }
1522
1510
  function checkIn(options) {
1523
1511
  return client ? sendCheckIn(client, options) : void 0;
@@ -1539,17 +1527,17 @@ function register(task, handler) {
1539
1527
  if (registry.has(task)) {
1540
1528
  (agent?.logger ?? client?.logger)?.warn(`register('${task}') sobrescreveu um handler existente`);
1541
1529
  }
1542
- if (client && !agent && !warnedManagerDisabled) {
1543
- warnedManagerDisabled = true;
1530
+ if (client && !agent && !warnedTasksDisabled) {
1531
+ warnedTasksDisabled = true;
1544
1532
  client.logger.warn(
1545
- `register('${task}') com o Manager desabilitado \u2014 passe manager: true (ou op\xE7\xF5es) no init()`
1533
+ `register('${task}') com tasks desabilitadas \u2014 passe tasks: true (ou op\xE7\xF5es) no init()`
1546
1534
  );
1547
1535
  }
1548
1536
  registry.set(task, handler);
1549
1537
  }
1550
1538
  function start() {
1551
1539
  if (!agent) {
1552
- throw new Error("[hobots] Manager n\xE3o configurado \u2014 passe manager: true (ou op\xE7\xF5es) no init() antes de start()");
1540
+ throw new Error("[hobots] tasks n\xE3o configuradas \u2014 passe tasks: true (ou op\xE7\xF5es) no init() antes de start()");
1553
1541
  }
1554
1542
  agent.start();
1555
1543
  }
@@ -1566,9 +1554,6 @@ async function close(timeoutMs) {
1566
1554
  export {
1567
1555
  Client,
1568
1556
  DEFAULT_BASE_URL,
1569
- ManagerAgent,
1570
- ManagerApi,
1571
- ManagerApiError,
1572
1557
  Scope,
1573
1558
  Span,
1574
1559
  Transaction,
@@ -1581,7 +1566,6 @@ export {
1581
1566
  getClient,
1582
1567
  init,
1583
1568
  register,
1584
- resolveConfig,
1585
1569
  scrubSecrets,
1586
1570
  setContext,
1587
1571
  setExtra,