hobots 5.2.0 → 6.1.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.d.cts CHANGED
@@ -270,14 +270,17 @@ interface RunItemOptions extends RunItemImageOptions {
270
270
  id?: string;
271
271
  }
272
272
  /**
273
- * O item em processamento dentro de `items.each`: o desfecho (`succeeded`/`failed`/
273
+ * O item em processamento dentro de `items.each` — o único argumento do handler: o elemento
274
+ * em `value`, a posição em `index`, o desfecho (`succeeded`/`failed`/
274
275
  * `occurrence`) com o `id` e o payload base já resolvidos, e o handle `step` da etapa do
275
276
  * item (`warning`/`image`/`data`), e `data(...)` para ir guardando o payload do item sem
276
277
  * decidir o desfecho. Sub-etapas: `hobots.step(...)` dentro do handler já
277
278
  * aninha na etapa do item. Sem chamada explícita, o SDK decide: retornou → `succeeded`;
278
279
  * lançou → `failed` com a mensagem do erro.
279
280
  */
280
- interface RunItemHandle {
281
+ interface RunItemHandle<T = unknown> {
282
+ /** O elemento da lista — o mesmo em todas as tentativas. */
283
+ readonly value: T;
281
284
  /** Posição do elemento na lista (0-based). */
282
285
  readonly index: number;
283
286
  /** Identidade do item: a resolvida por `options.id` ou, sem ela, uma gerada pelo SDK — a mesma em todas as tentativas. */
@@ -288,6 +291,8 @@ interface RunItemHandle {
288
291
  readonly reported: boolean;
289
292
  /** Desfecho registrado até agora (undefined = nenhum). */
290
293
  readonly status: RunItemStatus | undefined;
294
+ /** Motivo do desfecho (`failed`/`occurrence`) — vai na linha `[item] FINISH` do log. */
295
+ readonly message: string | undefined;
291
296
  /** Tentativa em curso (1-based) — o handler pode ler para logar. */
292
297
  readonly attempt: number;
293
298
  /**
@@ -313,8 +318,13 @@ interface RunItemsEachOptions<T> {
313
318
  * no elemento é erro do bot (lança e interrompe o loop).
314
319
  */
315
320
  id?: string | readonly string[] | ((element: T, index: number) => string | number | null | undefined) | null;
316
- /** Atributos base do item (viram colunas no app) — presentes em qualquer desfecho, inclusive falha. */
317
- payload?: (element: T, index: number) => Record<string, unknown> | undefined;
321
+ /**
322
+ * Atributos base do item (viram colunas no app) — presentes em qualquer desfecho, inclusive falha.
323
+ * Omitido: os campos do próprio elemento quando ele é um objeto simples (`{ id, cliente, … }`,
324
+ * cópia rasa); instância de classe, array e primitivo ficam sem payload base. Uma função substitui
325
+ * esse padrão (escolhe/renomeia as colunas); `false` desliga.
326
+ */
327
+ payload?: ((element: T, index: number) => Record<string, unknown> | undefined) | false;
318
328
  /** `op` da etapa de cada item no waterfall. Default: 'item'. */
319
329
  op?: string;
320
330
  /** Descrição da etapa de cada item. Default: o `id`, senão `#<posição>`. */
@@ -351,7 +361,11 @@ interface RunItemsEachResult {
351
361
  /** Já concluídos neste trabalho (reexecução): não rodaram, mas contam no `total`. */
352
362
  skipped: number;
353
363
  }
354
- type RunItemsEachHandler<T> = (element: T, item: RunItemHandle, index: number) => unknown;
364
+ /**
365
+ * Handler do `items.each` (6.0.0): `(item, index)` — o elemento em `item.value`; `index` (0-based,
366
+ * o mesmo que `item.index`) é opcional, declare só se usar.
367
+ */
368
+ type RunItemsEachHandler<T> = (item: RunItemHandle<T>, index: number) => unknown;
355
369
  /**
356
370
  * Registro dos itens que a execução processou/gerou (a entrega do bot: notas
357
371
  * fiscais baixadas, boletos emitidos...). Chamadas são síncronas — os itens são
package/dist/index.d.ts CHANGED
@@ -270,14 +270,17 @@ interface RunItemOptions extends RunItemImageOptions {
270
270
  id?: string;
271
271
  }
272
272
  /**
273
- * O item em processamento dentro de `items.each`: o desfecho (`succeeded`/`failed`/
273
+ * O item em processamento dentro de `items.each` — o único argumento do handler: o elemento
274
+ * em `value`, a posição em `index`, o desfecho (`succeeded`/`failed`/
274
275
  * `occurrence`) com o `id` e o payload base já resolvidos, e o handle `step` da etapa do
275
276
  * item (`warning`/`image`/`data`), e `data(...)` para ir guardando o payload do item sem
276
277
  * decidir o desfecho. Sub-etapas: `hobots.step(...)` dentro do handler já
277
278
  * aninha na etapa do item. Sem chamada explícita, o SDK decide: retornou → `succeeded`;
278
279
  * lançou → `failed` com a mensagem do erro.
279
280
  */
280
- interface RunItemHandle {
281
+ interface RunItemHandle<T = unknown> {
282
+ /** O elemento da lista — o mesmo em todas as tentativas. */
283
+ readonly value: T;
281
284
  /** Posição do elemento na lista (0-based). */
282
285
  readonly index: number;
283
286
  /** Identidade do item: a resolvida por `options.id` ou, sem ela, uma gerada pelo SDK — a mesma em todas as tentativas. */
@@ -288,6 +291,8 @@ interface RunItemHandle {
288
291
  readonly reported: boolean;
289
292
  /** Desfecho registrado até agora (undefined = nenhum). */
290
293
  readonly status: RunItemStatus | undefined;
294
+ /** Motivo do desfecho (`failed`/`occurrence`) — vai na linha `[item] FINISH` do log. */
295
+ readonly message: string | undefined;
291
296
  /** Tentativa em curso (1-based) — o handler pode ler para logar. */
292
297
  readonly attempt: number;
293
298
  /**
@@ -313,8 +318,13 @@ interface RunItemsEachOptions<T> {
313
318
  * no elemento é erro do bot (lança e interrompe o loop).
314
319
  */
315
320
  id?: string | readonly string[] | ((element: T, index: number) => string | number | null | undefined) | null;
316
- /** Atributos base do item (viram colunas no app) — presentes em qualquer desfecho, inclusive falha. */
317
- payload?: (element: T, index: number) => Record<string, unknown> | undefined;
321
+ /**
322
+ * Atributos base do item (viram colunas no app) — presentes em qualquer desfecho, inclusive falha.
323
+ * Omitido: os campos do próprio elemento quando ele é um objeto simples (`{ id, cliente, … }`,
324
+ * cópia rasa); instância de classe, array e primitivo ficam sem payload base. Uma função substitui
325
+ * esse padrão (escolhe/renomeia as colunas); `false` desliga.
326
+ */
327
+ payload?: ((element: T, index: number) => Record<string, unknown> | undefined) | false;
318
328
  /** `op` da etapa de cada item no waterfall. Default: 'item'. */
319
329
  op?: string;
320
330
  /** Descrição da etapa de cada item. Default: o `id`, senão `#<posição>`. */
@@ -351,7 +361,11 @@ interface RunItemsEachResult {
351
361
  /** Já concluídos neste trabalho (reexecução): não rodaram, mas contam no `total`. */
352
362
  skipped: number;
353
363
  }
354
- type RunItemsEachHandler<T> = (element: T, item: RunItemHandle, index: number) => unknown;
364
+ /**
365
+ * Handler do `items.each` (6.0.0): `(item, index)` — o elemento em `item.value`; `index` (0-based,
366
+ * o mesmo que `item.index`) é opcional, declare só se usar.
367
+ */
368
+ type RunItemsEachHandler<T> = (item: RunItemHandle<T>, index: number) => unknown;
355
369
  /**
356
370
  * Registro dos itens que a execução processou/gerou (a entrega do bot: notas
357
371
  * fiscais baixadas, boletos emitidos...). Chamadas são síncronas — os itens são
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
 
5
5
  // src/utils.ts
6
6
  var SDK_NAME = "hobots";
7
- var SDK_VERSION = true ? "5.2.0" : "0.0.0-dev";
7
+ var SDK_VERSION = true ? "6.1.0" : "0.0.0-dev";
8
8
  function isNode() {
9
9
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
10
10
  }
@@ -1479,6 +1479,10 @@ function stepId() {
1479
1479
  function stepLabel(op, description) {
1480
1480
  return [op, description].filter(Boolean).join(" \u2014 ") || "(sem nome)";
1481
1481
  }
1482
+ var ITEM_OP = "item";
1483
+ function lifecycleLabel(kind, op, description) {
1484
+ return stepLabel(kind === "item" && op === ITEM_OP ? void 0 : op, description);
1485
+ }
1482
1486
  function errorText(error) {
1483
1487
  if (error instanceof Error) return error.message || error.name;
1484
1488
  return safeString(error);
@@ -1511,21 +1515,25 @@ function colorForStatus(status) {
1511
1515
  }
1512
1516
  }
1513
1517
  var Step = class {
1514
- constructor(transaction, parentStepId, op, description) {
1518
+ constructor(transaction, parentStepId, op, description, kind = "step") {
1515
1519
  this.transaction = transaction;
1516
1520
  this.parentStepId = parentStepId;
1517
1521
  this.op = op;
1518
1522
  this.description = description;
1523
+ this.kind = kind;
1519
1524
  }
1520
1525
  transaction;
1521
1526
  parentStepId;
1522
1527
  op;
1523
1528
  description;
1529
+ kind;
1524
1530
  stepId = stepId();
1525
1531
  startTimestamp = nowISO();
1526
1532
  status = "ok";
1527
1533
  endTimestamp;
1528
1534
  _data;
1535
+ // desfecho que a linha de FINISH reporta no lugar do status da etapa (item do `each`)
1536
+ outcome;
1529
1537
  // ── API do bot ────────────────────────────────────────────────────────────
1530
1538
  warning(message, options) {
1531
1539
  if (this.status !== "error") this._setStatus("warning", message);
@@ -1564,6 +1572,13 @@ var Step = class {
1564
1572
  this._close();
1565
1573
  return this;
1566
1574
  }
1575
+ /**
1576
+ * Faz a linha de FINISH reportar `status`/`reason` em vez do status da etapa — o
1577
+ * `items.each` usa para o log do item seguir o desfecho dele (sucesso/falha/ocorrência).
1578
+ */
1579
+ _setOutcome(status, reason) {
1580
+ this.outcome = { status, reason };
1581
+ }
1567
1582
  _setData(key, value) {
1568
1583
  this._data = { ...this._data, [key]: value };
1569
1584
  }
@@ -1595,14 +1610,15 @@ var Step = class {
1595
1610
  _close() {
1596
1611
  if (this.endTimestamp) return;
1597
1612
  this.endTimestamp = nowISO();
1613
+ const { status, reason } = this.outcome ?? { status: this.status, reason: this.reason() };
1598
1614
  this.transaction.emit(
1599
- "step",
1615
+ this.kind,
1600
1616
  "finish",
1601
- stepLabel(this.op, this.description),
1617
+ lifecycleLabel(this.kind, this.op, this.description),
1602
1618
  this.startTimestamp,
1603
1619
  this.endTimestamp,
1604
- this.status,
1605
- this.reason()
1620
+ status,
1621
+ reason
1606
1622
  );
1607
1623
  }
1608
1624
  toJSON() {
@@ -1657,7 +1673,7 @@ var Transaction = class {
1657
1673
  emit(kind, phase, label, startTs, endTs, status, reason) {
1658
1674
  const durationMs = startTs && endTs ? new Date(endTs).getTime() - new Date(startTs).getTime() : void 0;
1659
1675
  const suffix = (durationMs != null ? ` (${durationMs}ms)` : "") + (reason ? `: ${reason}` : "");
1660
- const localFinish = this.echo && kind === "step" && phase === "finish";
1676
+ const localFinish = this.echo && kind !== "transaction" && phase === "finish";
1661
1677
  if (this.client?.debug || localFinish) {
1662
1678
  const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? colorForStatus(status ?? "ok") : "cyan");
1663
1679
  console.log(`${tag} ${label}${suffix}`);
@@ -1704,13 +1720,16 @@ var Transaction = class {
1704
1720
  if (!step) return fn();
1705
1721
  return runWithStepScope({ transaction: this, step }, fn);
1706
1722
  }
1707
- /** Abre uma etapa sob a corrente (ou direta da transaction). Interno: o bot usa `step()`. */
1708
- start(op, description) {
1723
+ /**
1724
+ * Abre uma etapa sob a corrente (ou direta da transaction). Interno: o bot usa `step()`; o
1725
+ * `items.each` abre com `kind: 'item'` (linhas `[item] START/FINISH`).
1726
+ */
1727
+ start(op, description, kind = "step") {
1709
1728
  this.stepGuard?.();
1710
1729
  const parent = this.currentStep();
1711
- const step = new Step(this, parent ? parent.stepId : this.stepId, op, description);
1730
+ const step = new Step(this, parent ? parent.stepId : this.stepId, op, description, kind);
1712
1731
  this.steps.push(step);
1713
- this.emit("step", "start", stepLabel(op, description));
1732
+ this.emit(kind, "start", lifecycleLabel(kind, op, description));
1714
1733
  return step;
1715
1734
  }
1716
1735
  /**
@@ -1857,13 +1876,15 @@ function thrownMessage(error) {
1857
1876
  return safeString(error);
1858
1877
  }
1859
1878
  var ItemHandle = class {
1860
- constructor(index, id, step, basePayload, sink) {
1879
+ constructor(value, index, id, step, basePayload2, sink) {
1880
+ this.value = value;
1861
1881
  this.index = index;
1862
1882
  this.step = step;
1863
- this.basePayload = basePayload;
1883
+ this.basePayload = basePayload2;
1864
1884
  this.sink = sink;
1865
1885
  this.id = id ?? uuid4();
1866
1886
  }
1887
+ value;
1867
1888
  index;
1868
1889
  step;
1869
1890
  basePayload;
@@ -1872,6 +1893,7 @@ var ItemHandle = class {
1872
1893
  id;
1873
1894
  reported = false;
1874
1895
  status;
1896
+ message;
1875
1897
  attempt = 1;
1876
1898
  accumulated = {};
1877
1899
  data(payload) {
@@ -1893,6 +1915,7 @@ var ItemHandle = class {
1893
1915
  this.attempt = attempt;
1894
1916
  this.reported = false;
1895
1917
  this.status = void 0;
1918
+ this.message = void 0;
1896
1919
  this.accumulated = {};
1897
1920
  if (this.step && (this.step.status === "error" || this.step.status === "warning")) {
1898
1921
  this.step._setStatus("ok");
@@ -1902,10 +1925,23 @@ var ItemHandle = class {
1902
1925
  record(status, message, payload, image3) {
1903
1926
  this.reported = true;
1904
1927
  this.status = status;
1928
+ this.message = message;
1905
1929
  const merged = { ...this.basePayload ?? {}, ...this.accumulated, ...payload ?? {} };
1906
1930
  this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id, image3);
1907
1931
  }
1908
1932
  };
1933
+ function defaultPayload(element) {
1934
+ if (typeof element !== "object" || element === null) return void 0;
1935
+ const proto = Object.getPrototypeOf(element);
1936
+ if (proto !== Object.prototype && proto !== null) return void 0;
1937
+ const copy = { ...element };
1938
+ return Object.keys(copy).length > 0 ? copy : void 0;
1939
+ }
1940
+ function basePayload(spec, element, index) {
1941
+ if (spec === false) return void 0;
1942
+ if (spec === void 0) return defaultPayload(element);
1943
+ return spec(element, index);
1944
+ }
1909
1945
  function field(element, name) {
1910
1946
  if (typeof element !== "object" || element === null) return { present: false, value: void 0 };
1911
1947
  const record = element;
@@ -1941,6 +1977,10 @@ function knownLength(list) {
1941
1977
  const length = list?.length;
1942
1978
  return typeof length === "number" && Number.isInteger(length) && length >= 1 ? length : void 0;
1943
1979
  }
1980
+ var ITEM_LOG_STATUS = { succeeded: "ok", occurrence: "warning", failed: "error" };
1981
+ function setItemOutcome(step, status, message) {
1982
+ step._setOutcome(ITEM_LOG_STATUS[status], status === "succeeded" ? void 0 : message);
1983
+ }
1944
1984
  function skippedMessage(count) {
1945
1985
  if (count === 1) return "1 item pulado: j\xE1 conclu\xEDdo neste trabalho numa execu\xE7\xE3o anterior";
1946
1986
  return `${count} itens pulados: j\xE1 conclu\xEDdos neste trabalho numa execu\xE7\xE3o anterior`;
@@ -1950,23 +1990,34 @@ async function eachItem(list, handler, options, sink) {
1950
1990
  const requested = options.retries ?? DEFAULT_EACH_RETRIES;
1951
1991
  const totalRetries = Number.isFinite(requested) ? Math.max(0, Math.floor(requested)) : DEFAULT_EACH_RETRIES;
1952
1992
  const explicitId = options.id !== void 0;
1953
- const length = options.progress === false ? void 0 : knownLength(list);
1954
- if (length !== void 0) sink.declareTotal(length);
1955
1993
  const done = options.skipDone === false ? void 0 : await sink.doneItemIds();
1956
- let index = 0;
1957
- for await (const element of list) {
1994
+ const isDone = (id) => id !== void 0 && done !== void 0 && done.has(id);
1995
+ const length = knownLength(list);
1996
+ let entries;
1997
+ if (length !== void 0 && done !== void 0 && done.size > 0) {
1998
+ const pending = [];
1999
+ let position = 0;
2000
+ for await (const element of list) {
2001
+ const id = resolveId(options.id, element, position, explicitId);
2002
+ if (isDone(id)) result.skipped += 1;
2003
+ else pending.push({ element, index: position, id });
2004
+ position += 1;
2005
+ }
2006
+ if (options.progress !== false && pending.length > 0) sink.declareTotal(pending.length);
2007
+ entries = pending;
2008
+ } else {
2009
+ if (options.progress !== false && length !== void 0) sink.declareTotal(length);
2010
+ entries = lazyEntries(list, options.id, explicitId);
2011
+ }
2012
+ for await (const { element, index, id } of entries) {
1958
2013
  sink.checkCanceled();
1959
- const id = resolveId(options.id, element, index, explicitId);
1960
- if (done?.size && id !== void 0 && done.has(id)) {
1961
- result.total += 1;
2014
+ if (isDone(id)) {
1962
2015
  result.skipped += 1;
1963
- if (options.progress !== false) sink.advance();
1964
- index += 1;
1965
2016
  continue;
1966
2017
  }
1967
2018
  const description = options.description?.(element, index) ?? id ?? `#${index + 1}`;
1968
2019
  const step = sink.startStep(options.op ?? "item", description);
1969
- const handle = new ItemHandle(index, id, step, options.payload?.(element, index), sink);
2020
+ const handle = new ItemHandle(element, index, id, step, basePayload(options.payload, element, index), sink);
1970
2021
  const current = index;
1971
2022
  let attempts = 0;
1972
2023
  let lastFailure;
@@ -1975,7 +2026,7 @@ async function eachItem(list, handler, options, sink) {
1975
2026
  (attempt) => {
1976
2027
  attempts = attempt;
1977
2028
  if (attempt > 1) handle._nextAttempt(attempt);
1978
- return sink.stepScope(step, async () => handler(element, handle, current));
2029
+ return sink.stepScope(step, async () => handler(handle, current));
1979
2030
  },
1980
2031
  {
1981
2032
  retries: totalRetries,
@@ -2011,20 +2062,34 @@ async function eachItem(list, handler, options, sink) {
2011
2062
  } else {
2012
2063
  const message = thrownMessage(error);
2013
2064
  if (!handle.reported) handle.failed(message);
2014
- step?._failed(error);
2065
+ if (step) {
2066
+ setItemOutcome(step, handle.status ?? "failed", handle.message);
2067
+ step._failed(error);
2068
+ }
2015
2069
  sink.logError(`item ${description} falhou: ${message}`);
2016
2070
  }
2017
2071
  }
2018
- step?._close();
2072
+ const final = handle.status ?? "succeeded";
2073
+ if (step) {
2074
+ setItemOutcome(step, final, handle.message);
2075
+ step._close();
2076
+ }
2019
2077
  if (options.progress !== false) sink.advance();
2020
2078
  result.total += 1;
2021
- result[handle.status ?? "succeeded"] += 1;
2022
- index += 1;
2079
+ result[final] += 1;
2023
2080
  if (options.flush !== false) await sink.flush();
2024
2081
  }
2082
+ result.total += result.skipped;
2025
2083
  if (result.skipped > 0) sink.logInfo(skippedMessage(result.skipped));
2026
2084
  return result;
2027
2085
  }
2086
+ async function* lazyEntries(list, spec, explicit) {
2087
+ let index = 0;
2088
+ for await (const element of list) {
2089
+ yield { element, index, id: resolveId(spec, element, index, explicit) };
2090
+ index += 1;
2091
+ }
2092
+ }
2028
2093
  function noteAttempts(step, attempts) {
2029
2094
  if (attempts > 1) step._setData("attempts", attempts);
2030
2095
  }
@@ -2410,7 +2475,7 @@ var RunReporter = class {
2410
2475
  tx.setStepGuard(checkCanceled);
2411
2476
  const eachSink = {
2412
2477
  report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
2413
- startStep: (op, description) => tx.start(op, description),
2478
+ startStep: (op, description) => tx.start(op, description, "item"),
2414
2479
  stepScope: (step, fn) => tx.scope(step, fn),
2415
2480
  declareTotal: (total) => {
2416
2481
  if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);