hobots 6.0.0 → 6.1.1

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/README.md CHANGED
@@ -231,6 +231,7 @@ Sem handler próprio de sinal, um `SIGINT`/`SIGTERM` (ou erro fatal) com execuç
231
231
 
232
232
  ## Changelog resumido
233
233
 
234
+ - **6.1.0** — cada item do `items.each` termina com uma linha `[item] FINISH <chave> (<ms>)` no log, no nível do desfecho (`success`/`error`/`warning`), no lugar do `[step] FINISH item — …`; `item.message` com o motivo. Detalhes no [CHANGELOG](./CHANGELOG.md).
234
235
  - **6.0.0** — **breaking**: o handler de `items.each` recebe o handle do item, `async (item, index) => …` (`index` opcional); o elemento da lista é `item.value` e a posição, `item.index`. Migração: `async (conta, item, i) => …` → `async (item, i) => { const conta = item.value; … }`. `options.id`/`payload`/`description` continuam recebendo `(element, index)`. **Payload padrão:** sem `options.payload`, os campos do elemento (objeto simples, cópia rasa) viram o payload base do item; `options.payload` substitui, `payload: false` desliga. Detalhes no [CHANGELOG](./CHANGELOG.md).
235
236
  - **5.2.0** — **`item.data(payload)`** no handle do `items.each`: guarda atributos do item sem registrar desfecho; mescla a cada chamada e entra em qualquer desfecho (inclusive nos implícitos), entre o `options.payload` do `each` e o payload da chamada de desfecho; zera na nova tentativa. Detalhes no [CHANGELOG](./CHANGELOG.md).
236
237
  - **5.1.0** — `items.each` com **nova tentativa por item** (`retries: 1`, `retryDelayMs: 5000`; uma etapa só por elemento, `warning` quando a repetição deu certo, `error` + item `failed` quando esgotou), **`Occurrence`** exportada (`throw new Occurrence(msg, payload?, { image }?)` no handler = item `occurrence`, sem repetir), `id` por campo (`'numero'`), chave composta (`['cnpj', 'competencia']`) ou função — padrão: o campo `id` do elemento — e **reexecução sem repetir**: ao executar novamente o trabalho, pula os ids já `succeeded`/`occurrence` (`skipDone: true`; `POST /v1/runs/{id}/items/done`); resultado com `skipped`; `item.id` sempre `string`, `item.attempt`. Detalhes no [CHANGELOG](./CHANGELOG.md).
package/dist/index.cjs CHANGED
@@ -45,7 +45,7 @@ module.exports = __toCommonJS(src_exports);
45
45
 
46
46
  // src/utils.ts
47
47
  var SDK_NAME = "hobots";
48
- var SDK_VERSION = true ? "6.0.0" : "0.0.0-dev";
48
+ var SDK_VERSION = true ? "6.1.1" : "0.0.0-dev";
49
49
  function isNode() {
50
50
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
51
51
  }
@@ -1547,6 +1547,10 @@ function stepId() {
1547
1547
  function stepLabel(op, description) {
1548
1548
  return [op, description].filter(Boolean).join(" \u2014 ") || "(sem nome)";
1549
1549
  }
1550
+ var ITEM_OP = "item";
1551
+ function lifecycleLabel(kind, op, description) {
1552
+ return stepLabel(kind === "item" && op === ITEM_OP ? void 0 : op, description);
1553
+ }
1550
1554
  function errorText(error) {
1551
1555
  if (error instanceof Error) return error.message || error.name;
1552
1556
  return safeString(error);
@@ -1579,21 +1583,25 @@ function colorForStatus(status) {
1579
1583
  }
1580
1584
  }
1581
1585
  var Step = class {
1582
- constructor(transaction, parentStepId, op, description) {
1586
+ constructor(transaction, parentStepId, op, description, kind = "step") {
1583
1587
  this.transaction = transaction;
1584
1588
  this.parentStepId = parentStepId;
1585
1589
  this.op = op;
1586
1590
  this.description = description;
1591
+ this.kind = kind;
1587
1592
  }
1588
1593
  transaction;
1589
1594
  parentStepId;
1590
1595
  op;
1591
1596
  description;
1597
+ kind;
1592
1598
  stepId = stepId();
1593
1599
  startTimestamp = nowISO();
1594
1600
  status = "ok";
1595
1601
  endTimestamp;
1596
1602
  _data;
1603
+ // desfecho que a linha de FINISH reporta no lugar do status da etapa (item do `each`)
1604
+ outcome;
1597
1605
  // ── API do bot ────────────────────────────────────────────────────────────
1598
1606
  warning(message, options) {
1599
1607
  if (this.status !== "error") this._setStatus("warning", message);
@@ -1632,6 +1640,13 @@ var Step = class {
1632
1640
  this._close();
1633
1641
  return this;
1634
1642
  }
1643
+ /**
1644
+ * Faz a linha de FINISH reportar `status`/`reason` em vez do status da etapa — o
1645
+ * `items.each` usa para o log do item seguir o desfecho dele (sucesso/falha/ocorrência).
1646
+ */
1647
+ _setOutcome(status, reason) {
1648
+ this.outcome = { status, reason };
1649
+ }
1635
1650
  _setData(key, value) {
1636
1651
  this._data = { ...this._data, [key]: value };
1637
1652
  }
@@ -1663,14 +1678,15 @@ var Step = class {
1663
1678
  _close() {
1664
1679
  if (this.endTimestamp) return;
1665
1680
  this.endTimestamp = nowISO();
1681
+ const { status, reason } = this.outcome ?? { status: this.status, reason: this.reason() };
1666
1682
  this.transaction.emit(
1667
- "step",
1683
+ this.kind,
1668
1684
  "finish",
1669
- stepLabel(this.op, this.description),
1685
+ lifecycleLabel(this.kind, this.op, this.description),
1670
1686
  this.startTimestamp,
1671
1687
  this.endTimestamp,
1672
- this.status,
1673
- this.reason()
1688
+ status,
1689
+ reason
1674
1690
  );
1675
1691
  }
1676
1692
  toJSON() {
@@ -1725,7 +1741,7 @@ var Transaction = class {
1725
1741
  emit(kind, phase, label, startTs, endTs, status, reason) {
1726
1742
  const durationMs = startTs && endTs ? new Date(endTs).getTime() - new Date(startTs).getTime() : void 0;
1727
1743
  const suffix = (durationMs != null ? ` (${durationMs}ms)` : "") + (reason ? `: ${reason}` : "");
1728
- const localFinish = this.echo && kind === "step" && phase === "finish";
1744
+ const localFinish = this.echo && kind !== "transaction" && phase === "finish";
1729
1745
  if (this.client?.debug || localFinish) {
1730
1746
  const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? colorForStatus(status ?? "ok") : "cyan");
1731
1747
  console.log(`${tag} ${label}${suffix}`);
@@ -1772,13 +1788,16 @@ var Transaction = class {
1772
1788
  if (!step) return fn();
1773
1789
  return runWithStepScope({ transaction: this, step }, fn);
1774
1790
  }
1775
- /** Abre uma etapa sob a corrente (ou direta da transaction). Interno: o bot usa `step()`. */
1776
- start(op, description) {
1791
+ /**
1792
+ * Abre uma etapa sob a corrente (ou direta da transaction). Interno: o bot usa `step()`; o
1793
+ * `items.each` abre com `kind: 'item'` (linhas `[item] START/FINISH`).
1794
+ */
1795
+ start(op, description, kind = "step") {
1777
1796
  this.stepGuard?.();
1778
1797
  const parent = this.currentStep();
1779
- const step = new Step(this, parent ? parent.stepId : this.stepId, op, description);
1798
+ const step = new Step(this, parent ? parent.stepId : this.stepId, op, description, kind);
1780
1799
  this.steps.push(step);
1781
- this.emit("step", "start", stepLabel(op, description));
1800
+ this.emit(kind, "start", lifecycleLabel(kind, op, description));
1782
1801
  return step;
1783
1802
  }
1784
1803
  /**
@@ -1942,6 +1961,7 @@ var ItemHandle = class {
1942
1961
  id;
1943
1962
  reported = false;
1944
1963
  status;
1964
+ message;
1945
1965
  attempt = 1;
1946
1966
  accumulated = {};
1947
1967
  data(payload) {
@@ -1963,6 +1983,7 @@ var ItemHandle = class {
1963
1983
  this.attempt = attempt;
1964
1984
  this.reported = false;
1965
1985
  this.status = void 0;
1986
+ this.message = void 0;
1966
1987
  this.accumulated = {};
1967
1988
  if (this.step && (this.step.status === "error" || this.step.status === "warning")) {
1968
1989
  this.step._setStatus("ok");
@@ -1972,6 +1993,7 @@ var ItemHandle = class {
1972
1993
  record(status, message, payload, image3) {
1973
1994
  this.reported = true;
1974
1995
  this.status = status;
1996
+ this.message = message;
1975
1997
  const merged = { ...this.basePayload ?? {}, ...this.accumulated, ...payload ?? {} };
1976
1998
  this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id, image3);
1977
1999
  }
@@ -2023,6 +2045,10 @@ function knownLength(list) {
2023
2045
  const length = list?.length;
2024
2046
  return typeof length === "number" && Number.isInteger(length) && length >= 1 ? length : void 0;
2025
2047
  }
2048
+ var ITEM_LOG_STATUS = { succeeded: "ok", occurrence: "warning", failed: "error" };
2049
+ function setItemOutcome(step, status, message) {
2050
+ step._setOutcome(ITEM_LOG_STATUS[status], status === "succeeded" ? void 0 : message);
2051
+ }
2026
2052
  function skippedMessage(count) {
2027
2053
  if (count === 1) return "1 item pulado: j\xE1 conclu\xEDdo neste trabalho numa execu\xE7\xE3o anterior";
2028
2054
  return `${count} itens pulados: j\xE1 conclu\xEDdos neste trabalho numa execu\xE7\xE3o anterior`;
@@ -2032,18 +2058,29 @@ async function eachItem(list, handler, options, sink) {
2032
2058
  const requested = options.retries ?? DEFAULT_EACH_RETRIES;
2033
2059
  const totalRetries = Number.isFinite(requested) ? Math.max(0, Math.floor(requested)) : DEFAULT_EACH_RETRIES;
2034
2060
  const explicitId = options.id !== void 0;
2035
- const length = options.progress === false ? void 0 : knownLength(list);
2036
- if (length !== void 0) sink.declareTotal(length);
2037
2061
  const done = options.skipDone === false ? void 0 : await sink.doneItemIds();
2038
- let index = 0;
2039
- for await (const element of list) {
2062
+ const isDone = (id) => id !== void 0 && done !== void 0 && done.has(id);
2063
+ const length = knownLength(list);
2064
+ let entries;
2065
+ if (length !== void 0 && done !== void 0 && done.size > 0) {
2066
+ const pending = [];
2067
+ let position = 0;
2068
+ for await (const element of list) {
2069
+ const id = resolveId(options.id, element, position, explicitId);
2070
+ if (isDone(id)) result.skipped += 1;
2071
+ else pending.push({ element, index: position, id });
2072
+ position += 1;
2073
+ }
2074
+ if (options.progress !== false && pending.length > 0) sink.declareTotal(pending.length);
2075
+ entries = pending;
2076
+ } else {
2077
+ if (options.progress !== false && length !== void 0) sink.declareTotal(length);
2078
+ entries = lazyEntries(list, options.id, explicitId);
2079
+ }
2080
+ for await (const { element, index, id } of entries) {
2040
2081
  sink.checkCanceled();
2041
- const id = resolveId(options.id, element, index, explicitId);
2042
- if (done?.size && id !== void 0 && done.has(id)) {
2043
- result.total += 1;
2082
+ if (isDone(id)) {
2044
2083
  result.skipped += 1;
2045
- if (options.progress !== false) sink.advance();
2046
- index += 1;
2047
2084
  continue;
2048
2085
  }
2049
2086
  const description = options.description?.(element, index) ?? id ?? `#${index + 1}`;
@@ -2093,20 +2130,35 @@ async function eachItem(list, handler, options, sink) {
2093
2130
  } else {
2094
2131
  const message = thrownMessage(error);
2095
2132
  if (!handle.reported) handle.failed(message);
2096
- step?._failed(error);
2097
- sink.logError(`item ${description} falhou: ${message}`);
2133
+ if (step) {
2134
+ setItemOutcome(step, handle.status ?? "failed", handle.message);
2135
+ step._failed(error);
2136
+ } else {
2137
+ sink.logError(`item ${description} falhou: ${message}`);
2138
+ }
2098
2139
  }
2099
2140
  }
2100
- step?._close();
2141
+ const final = handle.status ?? "succeeded";
2142
+ if (step) {
2143
+ setItemOutcome(step, final, handle.message);
2144
+ step._close();
2145
+ }
2101
2146
  if (options.progress !== false) sink.advance();
2102
2147
  result.total += 1;
2103
- result[handle.status ?? "succeeded"] += 1;
2104
- index += 1;
2148
+ result[final] += 1;
2105
2149
  if (options.flush !== false) await sink.flush();
2106
2150
  }
2151
+ result.total += result.skipped;
2107
2152
  if (result.skipped > 0) sink.logInfo(skippedMessage(result.skipped));
2108
2153
  return result;
2109
2154
  }
2155
+ async function* lazyEntries(list, spec, explicit) {
2156
+ let index = 0;
2157
+ for await (const element of list) {
2158
+ yield { element, index, id: resolveId(spec, element, index, explicit) };
2159
+ index += 1;
2160
+ }
2161
+ }
2110
2162
  function noteAttempts(step, attempts) {
2111
2163
  if (attempts > 1) step._setData("attempts", attempts);
2112
2164
  }
@@ -2492,7 +2544,7 @@ var RunReporter = class {
2492
2544
  tx.setStepGuard(checkCanceled);
2493
2545
  const eachSink = {
2494
2546
  report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
2495
- startStep: (op, description) => tx.start(op, description),
2547
+ startStep: (op, description) => tx.start(op, description, "item"),
2496
2548
  stepScope: (step, fn) => tx.scope(step, fn),
2497
2549
  declareTotal: (total) => {
2498
2550
  if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
@@ -2502,7 +2554,9 @@ var RunReporter = class {
2502
2554
  isAborted: () => this.abortController.signal.aborted,
2503
2555
  logInfo: (message) => this.log("info", message),
2504
2556
  logWarning: (message) => this.log("warning", message),
2557
+ /* v8 ignore start -- a run sempre tem `tx`: o item tem etapa e o erro sai na linha `[item] FINISH` (o each só chama logError sem etapa) */
2505
2558
  logError: (message) => this.log("error", message),
2559
+ /* v8 ignore stop */
2506
2560
  sleep: (ms) => cancelableSleep(ms, this.abortController.signal),
2507
2561
  doneItemIds: () => this.listDoneItemIds(),
2508
2562
  flush: () => this.flush()