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.cjs CHANGED
@@ -32,9 +32,6 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  Client: () => Client,
34
34
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
35
- ManagerAgent: () => ManagerAgent,
36
- ManagerApi: () => ManagerApi,
37
- ManagerApiError: () => ManagerApiError,
38
35
  Scope: () => Scope,
39
36
  Span: () => Span,
40
37
  Transaction: () => Transaction,
@@ -47,7 +44,6 @@ __export(src_exports, {
47
44
  getClient: () => getClient,
48
45
  init: () => init,
49
46
  register: () => register,
50
- resolveConfig: () => resolveConfig,
51
47
  scrubSecrets: () => scrubSecrets,
52
48
  setContext: () => setContext,
53
49
  setExtra: () => setExtra,
@@ -64,7 +60,7 @@ module.exports = __toCommonJS(src_exports);
64
60
 
65
61
  // src/config.ts
66
62
  var DEFAULT_BASE_URL = "https://api2.hobots.app";
67
- var SECRET_PATTERN = /^(hb_)?[a-f0-9]{64}$/i;
63
+ var SECRET_PATTERN = /^hb_[a-f0-9]{64}$/i;
68
64
  function envBaseUrl() {
69
65
  if (typeof process === "undefined" || !process.env) return void 0;
70
66
  return process.env.HOBOTS_API_URL?.trim() || void 0;
@@ -109,7 +105,7 @@ function resolveConfig(options, prefix = "[hobots]") {
109
105
  key,
110
106
  baseUrl,
111
107
  ingestEndpoint: `${baseUrl}/v1/ingest`,
112
- managerBaseUrl: `${baseUrl}/v1/manager`
108
+ tasksBaseUrl: `${baseUrl}/v1/tasks`
113
109
  };
114
110
  }
115
111
 
@@ -167,7 +163,7 @@ function scrubSecrets(text) {
167
163
 
168
164
  // src/utils.ts
169
165
  var SDK_NAME = "hobots";
170
- var SDK_VERSION = true ? "1.6.0" : "0.0.0-dev";
166
+ var SDK_VERSION = true ? "2.0.0" : "0.0.0-dev";
171
167
  function isNode() {
172
168
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
173
169
  }
@@ -203,6 +199,33 @@ function sleep(ms) {
203
199
  function jitter(baseMs) {
204
200
  return Math.floor(baseMs / 2 + Math.random() * (baseMs / 2));
205
201
  }
202
+ function truncate(value, max) {
203
+ return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
204
+ }
205
+ var DEFAULT_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
206
+ async function fetchWithRetry(send, options) {
207
+ const delays = options.delays ?? DEFAULT_RETRY_DELAYS_MS;
208
+ for (let attempt = 0; ; attempt++) {
209
+ let retriable;
210
+ let serverDelay;
211
+ try {
212
+ const response = await send();
213
+ if (response.ok) return response;
214
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) return response;
215
+ if (response.status === 429) serverDelay = retryAfterMs(response);
216
+ retriable = new Error(`HTTP ${response.status}`);
217
+ } catch (error) {
218
+ retriable = error;
219
+ }
220
+ const base = delays[attempt];
221
+ if (base === void 0) {
222
+ throw retriable instanceof Error ? retriable : new Error(String(retriable));
223
+ }
224
+ const delay = serverDelay ?? jitter(base);
225
+ options.logger.warn(`${options.label}: falha (${String(retriable)}), retry em ${delay}ms`);
226
+ await sleep(delay);
227
+ }
228
+ }
206
229
  var MAX_RETRY_AFTER_MS = 6e4;
207
230
  function retryAfterMs(response) {
208
231
  const header = response.headers.get("retry-after");
@@ -303,7 +326,6 @@ function eventFromMessage(message, level = "info") {
303
326
  }
304
327
 
305
328
  // src/transport.ts
306
- var RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
307
329
  var REQUEST_TIMEOUT_MS = 1e4;
308
330
  var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
309
331
  var DEFAULT_MAX_BATCH_SIZE = 10;
@@ -370,36 +392,26 @@ var Transport = class {
370
392
  return request;
371
393
  }
372
394
  async sendWithRetry(envelope) {
373
- for (let attempt = 0; ; attempt++) {
374
- let serverDelay;
375
- try {
376
- const response = await fetch(this.endpoint, {
395
+ let response;
396
+ try {
397
+ response = await fetchWithRetry(
398
+ () => fetch(this.endpoint, {
377
399
  method: "POST",
378
400
  headers: { "content-type": "application/json", authorization: `Bearer ${this.key}` },
379
401
  body: JSON.stringify(envelope),
380
402
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
381
- });
382
- if (response.ok) {
383
- this.logger.debug(`envelope enviado (${envelope.items.length} itens)`);
384
- return;
385
- }
386
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
387
- this.logger.error(`envelope rejeitado (HTTP ${response.status}), descartado`);
388
- return;
389
- }
390
- if (response.status === 429) serverDelay = retryAfterMs(response);
391
- throw new Error(`HTTP ${response.status}`);
392
- } catch (error) {
393
- const base = RETRY_DELAYS_MS[attempt];
394
- if (base === void 0) {
395
- this.logger.error("envelope descartado ap\xF3s retries:", error);
396
- return;
397
- }
398
- const delay = serverDelay ?? jitter(base);
399
- this.logger.warn(`falha no envio (${String(error)}), retry em ${delay}ms`);
400
- await sleep(delay);
401
- }
403
+ }),
404
+ { logger: this.logger, label: "envio de envelope" }
405
+ );
406
+ } catch (error) {
407
+ this.logger.error("envelope descartado ap\xF3s retries:", error);
408
+ return;
402
409
  }
410
+ if (response.ok) {
411
+ this.logger.debug(`envelope enviado (${envelope.items.length} itens)`);
412
+ return;
413
+ }
414
+ this.logger.error(`envelope rejeitado (HTTP ${response.status}), descartado`);
403
415
  }
404
416
  /** Envia o que estiver na fila e aguarda envios em andamento (até timeoutMs). */
405
417
  async flush(timeoutMs = 5e3) {
@@ -432,10 +444,10 @@ var Client = class {
432
444
  #options;
433
445
  #config;
434
446
  transport;
435
- constructor(options) {
447
+ constructor(options, config = resolveConfig(options)) {
436
448
  this.#options = options;
437
449
  this.logger = makeLogger(!!options.debug);
438
- this.#config = resolveConfig(options);
450
+ this.#config = config;
439
451
  this.transport = new Transport({
440
452
  endpoint: this.#config.ingestEndpoint,
441
453
  key: this.#config.key,
@@ -841,50 +853,31 @@ function sendCheckIn(client2, options) {
841
853
  return checkInId;
842
854
  }
843
855
  async function withMonitorImpl(client2, captureError, monitorSlug, fn, options) {
844
- const checkInId = sendCheckIn(client2, {
845
- monitorSlug,
846
- status: "in_progress",
847
- schedule: options?.schedule,
848
- timezone: options?.timezone
849
- });
856
+ const base = { monitorSlug, schedule: options?.schedule, timezone: options?.timezone };
857
+ const checkInId = sendCheckIn(client2, { ...base, status: "in_progress" });
850
858
  const startedAt = Date.now();
851
859
  try {
852
860
  const result = await fn();
853
- sendCheckIn(client2, {
854
- monitorSlug,
855
- status: "ok",
856
- checkInId,
857
- durationMs: Date.now() - startedAt,
858
- schedule: options?.schedule,
859
- timezone: options?.timezone
860
- });
861
+ sendCheckIn(client2, { ...base, status: "ok", checkInId, durationMs: Date.now() - startedAt });
861
862
  return result;
862
863
  } catch (error) {
863
- sendCheckIn(client2, {
864
- monitorSlug,
865
- status: "error",
866
- checkInId,
867
- durationMs: Date.now() - startedAt,
868
- schedule: options?.schedule,
869
- timezone: options?.timezone
870
- });
864
+ sendCheckIn(client2, { ...base, status: "error", checkInId, durationMs: Date.now() - startedAt });
871
865
  captureError(error);
872
866
  throw error;
873
867
  }
874
868
  }
875
869
 
876
- // src/manager/api.ts
877
- var RETRY_DELAYS_MS2 = [1e3, 4e3, 16e3];
870
+ // src/tasks/api.ts
878
871
  var POLL_TIMEOUT_MS = 3e4;
879
872
  var UPDATE_TIMEOUT_MS = 1e4;
880
- var ManagerApiError = class extends Error {
873
+ var TasksApiError = class extends Error {
881
874
  constructor(message, status) {
882
875
  super(message);
883
876
  this.status = status;
884
- this.name = "ManagerApiError";
877
+ this.name = "TasksApiError";
885
878
  }
886
879
  };
887
- var ManagerApi = class {
880
+ var TasksApi = class {
888
881
  baseUrl;
889
882
  key;
890
883
  logger;
@@ -914,7 +907,7 @@ var ManagerApi = class {
914
907
  cleanup();
915
908
  }
916
909
  if (!response.ok) {
917
- throw new ManagerApiError(`poll falhou (HTTP ${response.status})`, response.status);
910
+ throw new TasksApiError(`poll falhou (HTTP ${response.status})`, response.status);
918
911
  }
919
912
  return this.parsePollResponse(await response.json(), request.max ?? 1);
920
913
  }
@@ -922,7 +915,7 @@ var ManagerApi = class {
922
915
  parsePollResponse(body, max) {
923
916
  const runs = body?.runs;
924
917
  if (!Array.isArray(runs)) {
925
- throw new ManagerApiError("resposta inv\xE1lida do poll (campo runs ausente)", 0);
918
+ throw new TasksApiError("resposta inv\xE1lida do poll (campo runs ausente)", 0);
926
919
  }
927
920
  const valid = [];
928
921
  for (const run of runs.slice(0, Math.max(0, max))) {
@@ -937,36 +930,25 @@ var ManagerApi = class {
937
930
  }
938
931
  /**
939
932
  * PATCH de status/logs com retry em falha de rede/5xx/429.
940
- * 4xx é definitivo e vira ManagerApiError imediato (409 = run cancelada/transição inválida).
933
+ * 4xx é definitivo e vira TasksApiError imediato (409 = run cancelada/transição inválida).
941
934
  */
942
935
  async updateRun(runId, update) {
943
- for (let attempt = 0; ; attempt++) {
944
- let retriable;
945
- let serverDelay;
946
- try {
947
- const response = await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
936
+ let response;
937
+ try {
938
+ response = await fetchWithRetry(
939
+ () => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
948
940
  method: "PATCH",
949
941
  headers: this.headers(),
950
942
  body: JSON.stringify(update),
951
943
  signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
952
- });
953
- if (response.ok) return;
954
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
955
- throw new ManagerApiError(`update rejeitado (HTTP ${response.status})`, response.status);
956
- }
957
- if (response.status === 429) serverDelay = retryAfterMs(response);
958
- retriable = new Error(`HTTP ${response.status}`);
959
- } catch (error) {
960
- if (error instanceof ManagerApiError) throw error;
961
- retriable = error;
962
- }
963
- const base = RETRY_DELAYS_MS2[attempt];
964
- if (base === void 0) {
965
- throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(retriable)}`);
966
- }
967
- const delay = serverDelay ?? jitter(base);
968
- this.logger.warn(`falha no update da run ${runId} (${String(retriable)}), retry em ${delay}ms`);
969
- await sleep(delay);
944
+ }),
945
+ { logger: this.logger, label: `update da run ${runId}` }
946
+ );
947
+ } catch (error) {
948
+ throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(error)}`);
949
+ }
950
+ if (!response.ok) {
951
+ throw new TasksApiError(`update rejeitado (HTTP ${response.status})`, response.status);
970
952
  }
971
953
  }
972
954
  /**
@@ -984,7 +966,7 @@ var ManagerApi = class {
984
966
  }
985
967
  };
986
968
 
987
- // src/manager/context.ts
969
+ // src/tasks/context.ts
988
970
  var LOG_LEVEL_COLOR = {
989
971
  debug: "gray",
990
972
  info: "cyan",
@@ -1095,8 +1077,7 @@ var RunReporter = class {
1095
1077
  return;
1096
1078
  }
1097
1079
  this.loggedCount++;
1098
- const truncated = scrubbed.length > MAX_LOG_MESSAGE_CHARS ? `${scrubbed.slice(0, MAX_LOG_MESSAGE_CHARS)}\u2026 [truncado]` : scrubbed;
1099
- this.buffer.push({ ts: nowISO(), level, message: truncated });
1080
+ this.buffer.push({ ts: nowISO(), level, message: truncate(scrubbed, MAX_LOG_MESSAGE_CHARS) });
1100
1081
  if (this.buffer.length >= FLUSH_AT_BUFFERED) {
1101
1082
  this.flushLogs();
1102
1083
  } else {
@@ -1187,7 +1168,7 @@ var RunReporter = class {
1187
1168
  try {
1188
1169
  await this.api.updateRun(this.runId, payload);
1189
1170
  } catch (error) {
1190
- if (error instanceof ManagerApiError && (error.status === 409 || error.status === 404)) {
1171
+ if (error instanceof TasksApiError && (error.status === 409 || error.status === 404)) {
1191
1172
  this.dead = true;
1192
1173
  this.stopCancelWatch();
1193
1174
  this.abort();
@@ -1203,7 +1184,7 @@ var RunReporter = class {
1203
1184
  }
1204
1185
  };
1205
1186
 
1206
- // src/manager/run-context-store.ts
1187
+ // src/tasks/run-context-store.ts
1207
1188
  var storage;
1208
1189
  var initPromise;
1209
1190
  async function ensureStorage() {
@@ -1213,15 +1194,15 @@ async function ensureStorage() {
1213
1194
  });
1214
1195
  await initPromise;
1215
1196
  }
1216
- async function runWithRunId(runId, fn) {
1197
+ async function runWithRunContext(store, fn) {
1217
1198
  await ensureStorage();
1218
- return storage ? storage.run({ runId }, fn) : fn();
1199
+ return storage ? storage.run(store, fn) : fn();
1219
1200
  }
1220
- function currentRunId() {
1221
- return storage?.getStore()?.runId;
1201
+ function currentRunTransaction() {
1202
+ return storage?.getStore()?.tx;
1222
1203
  }
1223
1204
 
1224
- // src/manager/shutdown.ts
1205
+ // src/tasks/shutdown.ts
1225
1206
  var WATCHED_SIGNALS = ["SIGINT", "SIGTERM"];
1226
1207
  function installShutdownHandlers(agent2) {
1227
1208
  if (!isNode()) return () => {
@@ -1265,7 +1246,7 @@ function installShutdownHandlers(agent2) {
1265
1246
  };
1266
1247
  }
1267
1248
 
1268
- // src/manager/agent.ts
1249
+ // src/tasks/agent.ts
1269
1250
  var DEFAULT_POLL_INTERVAL_MS = 5e3;
1270
1251
  var MIN_POLL_INTERVAL_MS = 1e3;
1271
1252
  var MAX_CONCURRENCY = 5;
@@ -1275,13 +1256,13 @@ function hostname() {
1275
1256
  if (!isNode()) return void 0;
1276
1257
  return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
1277
1258
  }
1278
- var ManagerAgent = class {
1259
+ var TaskAgent = class {
1279
1260
  constructor(options, registry2) {
1280
1261
  this.options = options;
1281
1262
  this.registry = registry2;
1282
- this.logger = makeLogger(options.debug ?? false, "[hobots]");
1283
- const config = resolveConfig(options, "[hobots]");
1284
- this.api = new ManagerApi({ baseUrl: config.managerBaseUrl, key: config.key, logger: this.logger });
1263
+ this.logger = makeLogger(options.debug ?? false);
1264
+ const config = options.config;
1265
+ this.api = new TasksApi({ baseUrl: config.tasksBaseUrl, key: config.key, logger: this.logger });
1285
1266
  this.client = options.client;
1286
1267
  this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
1287
1268
  this.concurrency = Math.min(MAX_CONCURRENCY, Math.max(1, options.concurrency ?? 1));
@@ -1289,7 +1270,7 @@ var ManagerAgent = class {
1289
1270
  this.taskTimeoutMs = options.taskTimeoutMs;
1290
1271
  this.logFlushIntervalMs = options.logFlushIntervalMs;
1291
1272
  this.cancelCheckIntervalMs = options.cancelCheckIntervalMs;
1292
- this.logger.debug("agente configurado", { baseUrl: config.managerBaseUrl });
1273
+ this.logger.debug("agente configurado", { baseUrl: config.tasksBaseUrl });
1293
1274
  }
1294
1275
  logger;
1295
1276
  api;
@@ -1411,7 +1392,7 @@ var ManagerAgent = class {
1411
1392
  }
1412
1393
  }
1413
1394
  delayAfterPollError(error) {
1414
- if (error instanceof ManagerApiError && error.status === 401) {
1395
+ if (error instanceof TasksApiError && error.status === 401) {
1415
1396
  this.consecutiveAuthFailures++;
1416
1397
  if (this.consecutiveAuthFailures === 1 || this.consecutiveAuthFailures % 10 === 0) {
1417
1398
  this.logger.error("chave rejeitada (401) \u2014 verifique o Client Secret do projeto em Configura\xE7\xF5es \u2192 Projetos");
@@ -1456,7 +1437,7 @@ var ManagerAgent = class {
1456
1437
  }
1457
1438
  this.logger.debug(`executando run ${run.id} (${run.task})`);
1458
1439
  try {
1459
- await runWithRunId(run.id, () => this.invokeWithTimeout(handler, run, reporter));
1440
+ await runWithRunContext({ runId: run.id, tx }, () => this.invokeWithTimeout(handler, run, reporter));
1460
1441
  reporter.reportSuccess();
1461
1442
  tx.setStatus("ok");
1462
1443
  this.logger.debug(`run ${run.id} conclu\xEDda`);
@@ -1491,9 +1472,6 @@ var ManagerAgent = class {
1491
1472
  var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
1492
1473
  var MAX_ERROR_MESSAGE_CHARS = 1e4;
1493
1474
  var MAX_ERROR_STACK_CHARS = 2e4;
1494
- function truncate(value, max) {
1495
- return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
1496
- }
1497
1475
  function toRunError(error) {
1498
1476
  if (error instanceof Error) {
1499
1477
  return {
@@ -1512,7 +1490,7 @@ var scope = new Scope();
1512
1490
  var heartbeat;
1513
1491
  var agent;
1514
1492
  var registry = /* @__PURE__ */ new Map();
1515
- var warnedManagerDisabled = false;
1493
+ var warnedTasksDisabled = false;
1516
1494
  function init(options) {
1517
1495
  if (client) {
1518
1496
  client.logger.warn("init() chamado mais de uma vez \u2014 substituindo o client anterior");
@@ -1523,8 +1501,9 @@ function init(options) {
1523
1501
  void agent.stop(0);
1524
1502
  agent = void 0;
1525
1503
  }
1526
- warnedManagerDisabled = false;
1527
- client = new Client(options);
1504
+ warnedTasksDisabled = false;
1505
+ const config = resolveConfig(options);
1506
+ client = new Client(options, config);
1528
1507
  if (options.captureUnhandled !== false) {
1529
1508
  installGlobalHandlers(client, () => scope);
1530
1509
  }
@@ -1533,10 +1512,10 @@ function init(options) {
1533
1512
  heartbeat = new Heartbeat(client, heartbeatOptions, options.instanceId);
1534
1513
  heartbeat.start();
1535
1514
  }
1536
- if (options.manager) {
1537
- const managerOptions = options.manager === true ? {} : options.manager;
1538
- agent = new ManagerAgent(
1539
- { ...managerOptions, clientSecret: options.clientSecret, instanceId: options.instanceId, debug: options.debug, client },
1515
+ if (options.tasks) {
1516
+ const tasksOptions = options.tasks === true ? {} : options.tasks;
1517
+ agent = new TaskAgent(
1518
+ { ...tasksOptions, config, instanceId: options.instanceId, debug: options.debug, client },
1540
1519
  registry
1541
1520
  );
1542
1521
  }
@@ -1579,9 +1558,14 @@ function withScope(callback) {
1579
1558
  }
1580
1559
  }
1581
1560
  function startTransaction(name, op) {
1561
+ const active = currentRunTransaction();
1562
+ if (active) {
1563
+ client?.logger.debug(`startTransaction('${name}') dentro de uma execu\xE7\xE3o \u2014 reutilizando a transaction da run`);
1564
+ return active;
1565
+ }
1582
1566
  const rate = client?.options.tracesSampleRate ?? 1;
1583
1567
  const sampled = rate >= 1 || Math.random() < rate;
1584
- return new Transaction(client, name, op, sampled, currentRunId());
1568
+ return new Transaction(client, name, op, sampled);
1585
1569
  }
1586
1570
  function checkIn(options) {
1587
1571
  return client ? sendCheckIn(client, options) : void 0;
@@ -1603,17 +1587,17 @@ function register(task, handler) {
1603
1587
  if (registry.has(task)) {
1604
1588
  (agent?.logger ?? client?.logger)?.warn(`register('${task}') sobrescreveu um handler existente`);
1605
1589
  }
1606
- if (client && !agent && !warnedManagerDisabled) {
1607
- warnedManagerDisabled = true;
1590
+ if (client && !agent && !warnedTasksDisabled) {
1591
+ warnedTasksDisabled = true;
1608
1592
  client.logger.warn(
1609
- `register('${task}') com o Manager desabilitado \u2014 passe manager: true (ou op\xE7\xF5es) no init()`
1593
+ `register('${task}') com tasks desabilitadas \u2014 passe tasks: true (ou op\xE7\xF5es) no init()`
1610
1594
  );
1611
1595
  }
1612
1596
  registry.set(task, handler);
1613
1597
  }
1614
1598
  function start() {
1615
1599
  if (!agent) {
1616
- throw new Error("[hobots] Manager n\xE3o configurado \u2014 passe manager: true (ou op\xE7\xF5es) no init() antes de start()");
1600
+ throw new Error("[hobots] tasks n\xE3o configuradas \u2014 passe tasks: true (ou op\xE7\xF5es) no init() antes de start()");
1617
1601
  }
1618
1602
  agent.start();
1619
1603
  }