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/README.md +3 -1
- package/dist/index.cjs +94 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -5
- package/dist/index.d.ts +19 -5
- package/dist/index.js +94 -29
- package/dist/index.js.map +1 -1
- package/docs/api-reference.md +1 -1
- package/docs/runs.md +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ A opção passada em `register()`/`start()` tem precedência sobre a env — út
|
|
|
112
112
|
| --- | --- |
|
|
113
113
|
| `hobots.log.debug/info/warn/error/success(msg, ...rest)` | Log da execução, enviado incrementalmente ao app (e ecoado no terminal como `[hobots] INFO …`). Aceita vários argumentos como `console.log` — `hobots.log.info('nota', numero, 'ok')` — cada um vira texto, unidos por espaço. Um `hobots.image(...)` como **último** argumento pendura um print na linha. **Nunca lança** — pode ir em `finally`. |
|
|
114
114
|
| `hobots.items.succeeded(payload?, { id, image }?)` · `.failed(msg, payload?, …)` · `.occurrence(msg, payload?, …)` · `.add({...})` | **A entrega do robô**: cada item processado, com status e atributos (viram colunas no app — declare-as com a opção `items: { columns: [...] }` do `register`/`start`). `image` pendura um print no item. Bufferizado, em lotes. |
|
|
115
|
-
| `hobots.items.each(lista, async (
|
|
115
|
+
| `hobots.items.each(lista, async (item, index?) => …, { id: 'campo', payload, retries: 1, retryDelayMs: 5000, skipDone: true }?)` | **Loop com o contrato garantido**: o handler recebe o handle do item (e, se declarar, o índice) — o elemento é `item.value` e a posição, `item.index`; sem `payload`, os campos do elemento (objeto simples) viram as colunas do item (`payload: false` desliga); cada elemento vira etapa + item; exceção no handler = nova tentativa e, esgotada, item `failed` com a mensagem e o loop **segue** até o fim; `throw new Occurrence(msg)` = item `occurrence`, sem repetir; na reexecução do mesmo trabalho pula o que já deu `succeeded`/`occurrence`; progresso automático; `hobots.flush()` ao fim de cada ciclo (`flush: false` desliga). Só cancelamento/timeout interrompem. No handler, `item.data({ nf: 123 })` vai guardando os atributos do item sem decidir o desfecho — entram no `succeeded`/`failed`/`occurrence`, inclusive nos implícitos. |
|
|
116
116
|
| `hobots.progress.total(n)` · `.advance(k = 1)` · `.set(atual, total?)` · `.current` · `.snapshot` | Barra, percentual e tempo restante na execução. Sem `total()` nada é enviado. |
|
|
117
117
|
| `hobots.flush(timeoutMs?)` | **Entrega já** o que a execução fez: as etapas raiz finalizadas viram transactions parciais (waterfall na Central sem esperar a run terminar) e itens, logs e progresso pendentes saem sem esperar o throttle. `step` (raiz) e cada ciclo do `items.each` já chamam sozinhos; à mão, para o resto da rodada de um agente `start()`. Aguarda com teto (15 s) e nunca lança. |
|
|
118
118
|
| `hobots.step(nome, async etapa => …, { retries = 1, retryDelayMs = 5_000, flush = true }?)` | Uma **etapa** no waterfall de performance da execução: roda a função, **tenta de novo** se ela lançar (`retries: 0` desliga — use em etapas não idempotentes) e fecha a etapa. Resolve com o retorno da função. `hobots.step` dentro de outra vira sub-etapa. A etapa raiz faz `flush()` ao terminar (`flush: false` desliga). O handle `etapa` tem `warning(motivo, { image }?)`, `image(png)` e `data(chave, valor)`. |
|
|
@@ -231,6 +231,8 @@ 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).
|
|
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).
|
|
234
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).
|
|
235
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).
|
|
236
238
|
- **5.0.0** — **uma API só de etapas** (breaking): `hobots.step(nome, fn, { retries, retryDelayMs }?)` abre a etapa, tenta de novo quando `fn` lança (default 1 tentativa extra) e fecha; `fn` recebe o handle `etapa` (`warning`/`image`/`data`); `hobots.step` dentro de outra (ou do handler do `items.each`) vira sub-etapa. `hobots.transaction`, `ctx.transaction` e os tipos `Transaction`/`Step`/`StepOutcomeOptions` foram removidos; `item.step` é o handle novo. Modo local ecoa `[hobots:step] FINISH …`. Nomes de etapa são recortados em 2048 no cliente. Flush automático ao fim da etapa raiz e de cada ciclo do `items.each` (`flush: false` desliga). Antes/depois 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 ? "
|
|
48
|
+
var SDK_VERSION = true ? "6.1.0" : "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
|
-
|
|
1683
|
+
this.kind,
|
|
1668
1684
|
"finish",
|
|
1669
|
-
|
|
1685
|
+
lifecycleLabel(this.kind, this.op, this.description),
|
|
1670
1686
|
this.startTimestamp,
|
|
1671
1687
|
this.endTimestamp,
|
|
1672
|
-
|
|
1673
|
-
|
|
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
|
|
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
|
-
/**
|
|
1776
|
-
|
|
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(
|
|
1800
|
+
this.emit(kind, "start", lifecycleLabel(kind, op, description));
|
|
1782
1801
|
return step;
|
|
1783
1802
|
}
|
|
1784
1803
|
/**
|
|
@@ -1925,13 +1944,15 @@ function thrownMessage(error) {
|
|
|
1925
1944
|
return safeString(error);
|
|
1926
1945
|
}
|
|
1927
1946
|
var ItemHandle = class {
|
|
1928
|
-
constructor(index, id, step,
|
|
1947
|
+
constructor(value, index, id, step, basePayload2, sink) {
|
|
1948
|
+
this.value = value;
|
|
1929
1949
|
this.index = index;
|
|
1930
1950
|
this.step = step;
|
|
1931
|
-
this.basePayload =
|
|
1951
|
+
this.basePayload = basePayload2;
|
|
1932
1952
|
this.sink = sink;
|
|
1933
1953
|
this.id = id ?? uuid4();
|
|
1934
1954
|
}
|
|
1955
|
+
value;
|
|
1935
1956
|
index;
|
|
1936
1957
|
step;
|
|
1937
1958
|
basePayload;
|
|
@@ -1940,6 +1961,7 @@ var ItemHandle = class {
|
|
|
1940
1961
|
id;
|
|
1941
1962
|
reported = false;
|
|
1942
1963
|
status;
|
|
1964
|
+
message;
|
|
1943
1965
|
attempt = 1;
|
|
1944
1966
|
accumulated = {};
|
|
1945
1967
|
data(payload) {
|
|
@@ -1961,6 +1983,7 @@ var ItemHandle = class {
|
|
|
1961
1983
|
this.attempt = attempt;
|
|
1962
1984
|
this.reported = false;
|
|
1963
1985
|
this.status = void 0;
|
|
1986
|
+
this.message = void 0;
|
|
1964
1987
|
this.accumulated = {};
|
|
1965
1988
|
if (this.step && (this.step.status === "error" || this.step.status === "warning")) {
|
|
1966
1989
|
this.step._setStatus("ok");
|
|
@@ -1970,10 +1993,23 @@ var ItemHandle = class {
|
|
|
1970
1993
|
record(status, message, payload, image3) {
|
|
1971
1994
|
this.reported = true;
|
|
1972
1995
|
this.status = status;
|
|
1996
|
+
this.message = message;
|
|
1973
1997
|
const merged = { ...this.basePayload ?? {}, ...this.accumulated, ...payload ?? {} };
|
|
1974
1998
|
this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id, image3);
|
|
1975
1999
|
}
|
|
1976
2000
|
};
|
|
2001
|
+
function defaultPayload(element) {
|
|
2002
|
+
if (typeof element !== "object" || element === null) return void 0;
|
|
2003
|
+
const proto = Object.getPrototypeOf(element);
|
|
2004
|
+
if (proto !== Object.prototype && proto !== null) return void 0;
|
|
2005
|
+
const copy = { ...element };
|
|
2006
|
+
return Object.keys(copy).length > 0 ? copy : void 0;
|
|
2007
|
+
}
|
|
2008
|
+
function basePayload(spec, element, index) {
|
|
2009
|
+
if (spec === false) return void 0;
|
|
2010
|
+
if (spec === void 0) return defaultPayload(element);
|
|
2011
|
+
return spec(element, index);
|
|
2012
|
+
}
|
|
1977
2013
|
function field(element, name) {
|
|
1978
2014
|
if (typeof element !== "object" || element === null) return { present: false, value: void 0 };
|
|
1979
2015
|
const record = element;
|
|
@@ -2009,6 +2045,10 @@ function knownLength(list) {
|
|
|
2009
2045
|
const length = list?.length;
|
|
2010
2046
|
return typeof length === "number" && Number.isInteger(length) && length >= 1 ? length : void 0;
|
|
2011
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
|
+
}
|
|
2012
2052
|
function skippedMessage(count) {
|
|
2013
2053
|
if (count === 1) return "1 item pulado: j\xE1 conclu\xEDdo neste trabalho numa execu\xE7\xE3o anterior";
|
|
2014
2054
|
return `${count} itens pulados: j\xE1 conclu\xEDdos neste trabalho numa execu\xE7\xE3o anterior`;
|
|
@@ -2018,23 +2058,34 @@ async function eachItem(list, handler, options, sink) {
|
|
|
2018
2058
|
const requested = options.retries ?? DEFAULT_EACH_RETRIES;
|
|
2019
2059
|
const totalRetries = Number.isFinite(requested) ? Math.max(0, Math.floor(requested)) : DEFAULT_EACH_RETRIES;
|
|
2020
2060
|
const explicitId = options.id !== void 0;
|
|
2021
|
-
const length = options.progress === false ? void 0 : knownLength(list);
|
|
2022
|
-
if (length !== void 0) sink.declareTotal(length);
|
|
2023
2061
|
const done = options.skipDone === false ? void 0 : await sink.doneItemIds();
|
|
2024
|
-
|
|
2025
|
-
|
|
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) {
|
|
2026
2081
|
sink.checkCanceled();
|
|
2027
|
-
|
|
2028
|
-
if (done?.size && id !== void 0 && done.has(id)) {
|
|
2029
|
-
result.total += 1;
|
|
2082
|
+
if (isDone(id)) {
|
|
2030
2083
|
result.skipped += 1;
|
|
2031
|
-
if (options.progress !== false) sink.advance();
|
|
2032
|
-
index += 1;
|
|
2033
2084
|
continue;
|
|
2034
2085
|
}
|
|
2035
2086
|
const description = options.description?.(element, index) ?? id ?? `#${index + 1}`;
|
|
2036
2087
|
const step = sink.startStep(options.op ?? "item", description);
|
|
2037
|
-
const handle = new ItemHandle(index, id, step, options.payload
|
|
2088
|
+
const handle = new ItemHandle(element, index, id, step, basePayload(options.payload, element, index), sink);
|
|
2038
2089
|
const current = index;
|
|
2039
2090
|
let attempts = 0;
|
|
2040
2091
|
let lastFailure;
|
|
@@ -2043,7 +2094,7 @@ async function eachItem(list, handler, options, sink) {
|
|
|
2043
2094
|
(attempt) => {
|
|
2044
2095
|
attempts = attempt;
|
|
2045
2096
|
if (attempt > 1) handle._nextAttempt(attempt);
|
|
2046
|
-
return sink.stepScope(step, async () => handler(
|
|
2097
|
+
return sink.stepScope(step, async () => handler(handle, current));
|
|
2047
2098
|
},
|
|
2048
2099
|
{
|
|
2049
2100
|
retries: totalRetries,
|
|
@@ -2079,20 +2130,34 @@ async function eachItem(list, handler, options, sink) {
|
|
|
2079
2130
|
} else {
|
|
2080
2131
|
const message = thrownMessage(error);
|
|
2081
2132
|
if (!handle.reported) handle.failed(message);
|
|
2082
|
-
step
|
|
2133
|
+
if (step) {
|
|
2134
|
+
setItemOutcome(step, handle.status ?? "failed", handle.message);
|
|
2135
|
+
step._failed(error);
|
|
2136
|
+
}
|
|
2083
2137
|
sink.logError(`item ${description} falhou: ${message}`);
|
|
2084
2138
|
}
|
|
2085
2139
|
}
|
|
2086
|
-
|
|
2140
|
+
const final = handle.status ?? "succeeded";
|
|
2141
|
+
if (step) {
|
|
2142
|
+
setItemOutcome(step, final, handle.message);
|
|
2143
|
+
step._close();
|
|
2144
|
+
}
|
|
2087
2145
|
if (options.progress !== false) sink.advance();
|
|
2088
2146
|
result.total += 1;
|
|
2089
|
-
result[
|
|
2090
|
-
index += 1;
|
|
2147
|
+
result[final] += 1;
|
|
2091
2148
|
if (options.flush !== false) await sink.flush();
|
|
2092
2149
|
}
|
|
2150
|
+
result.total += result.skipped;
|
|
2093
2151
|
if (result.skipped > 0) sink.logInfo(skippedMessage(result.skipped));
|
|
2094
2152
|
return result;
|
|
2095
2153
|
}
|
|
2154
|
+
async function* lazyEntries(list, spec, explicit) {
|
|
2155
|
+
let index = 0;
|
|
2156
|
+
for await (const element of list) {
|
|
2157
|
+
yield { element, index, id: resolveId(spec, element, index, explicit) };
|
|
2158
|
+
index += 1;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2096
2161
|
function noteAttempts(step, attempts) {
|
|
2097
2162
|
if (attempts > 1) step._setData("attempts", attempts);
|
|
2098
2163
|
}
|
|
@@ -2478,7 +2543,7 @@ var RunReporter = class {
|
|
|
2478
2543
|
tx.setStepGuard(checkCanceled);
|
|
2479
2544
|
const eachSink = {
|
|
2480
2545
|
report: (status, message, payload, id, image3) => this.addItem(status, message, payload, id, image3),
|
|
2481
|
-
startStep: (op, description) => tx.start(op, description),
|
|
2546
|
+
startStep: (op, description) => tx.start(op, description, "item"),
|
|
2482
2547
|
stepScope: (step, fn) => tx.scope(step, fn),
|
|
2483
2548
|
declareTotal: (total) => {
|
|
2484
2549
|
if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
|