@hostwebhook/node-sdk 0.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.
Files changed (59) hide show
  1. package/dist/code-runner.d.ts +20 -0
  2. package/dist/code-runner.js +138 -0
  3. package/dist/contratos.d.ts +121 -0
  4. package/dist/contratos.js +24 -0
  5. package/dist/dto/output-node.dto.d.ts +19 -0
  6. package/dist/dto/output-node.dto.js +96 -0
  7. package/dist/ensure-meta.d.ts +22 -0
  8. package/dist/ensure-meta.js +35 -0
  9. package/dist/execute-with-iteration.d.ts +18 -0
  10. package/dist/execute-with-iteration.js +66 -0
  11. package/dist/filter-utils.d.ts +22 -0
  12. package/dist/filter-utils.js +178 -0
  13. package/dist/handler-helpers.d.ts +21 -0
  14. package/dist/handler-helpers.js +53 -0
  15. package/dist/index.d.ts +51 -0
  16. package/dist/index.js +73 -0
  17. package/dist/log-metadata.d.ts +191 -0
  18. package/dist/log-metadata.js +375 -0
  19. package/dist/node-dispatch.registry.d.ts +32 -0
  20. package/dist/node-dispatch.registry.js +45 -0
  21. package/dist/node-executors.d.ts +299 -0
  22. package/dist/node-executors.js +555 -0
  23. package/dist/node-lifecycle.d.ts +399 -0
  24. package/dist/node-lifecycle.js +782 -0
  25. package/dist/normalize-nodes.d.ts +18 -0
  26. package/dist/normalize-nodes.js +22 -0
  27. package/dist/output-node-ref.schema.d.ts +82 -0
  28. package/dist/output-node-ref.schema.js +90 -0
  29. package/dist/output-webhook-scope.d.ts +36 -0
  30. package/dist/output-webhook-scope.js +42 -0
  31. package/dist/payload-preview.d.ts +10 -0
  32. package/dist/payload-preview.js +39 -0
  33. package/dist/pipeline.constants.d.ts +29 -0
  34. package/dist/pipeline.constants.js +51 -0
  35. package/dist/pre-request-pool.d.ts +58 -0
  36. package/dist/pre-request-pool.js +308 -0
  37. package/dist/pre-request-runner-source.d.ts +28 -0
  38. package/dist/pre-request-runner-source.js +411 -0
  39. package/dist/regex-de-inquilino.d.ts +15 -0
  40. package/dist/regex-de-inquilino.js +98 -0
  41. package/dist/request-context.d.ts +18 -0
  42. package/dist/request-context.js +34 -0
  43. package/dist/retry-transient.d.ts +54 -0
  44. package/dist/retry-transient.js +67 -0
  45. package/dist/retry-utils.d.ts +17 -0
  46. package/dist/retry-utils.js +23 -0
  47. package/dist/schema-validator-utils.d.ts +9 -0
  48. package/dist/schema-validator-utils.js +140 -0
  49. package/dist/ssrf-guard.d.ts +202 -0
  50. package/dist/ssrf-guard.js +917 -0
  51. package/dist/swallow.d.ts +52 -0
  52. package/dist/swallow.js +55 -0
  53. package/dist/template-render.d.ts +33 -0
  54. package/dist/template-render.js +43 -0
  55. package/dist/try-parse.d.ts +41 -0
  56. package/dist/try-parse.js +69 -0
  57. package/dist/workspace-payloads.d.ts +66 -0
  58. package/dist/workspace-payloads.js +496 -0
  59. package/package.json +35 -0
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getNestedValue = getNestedValue;
4
+ exports.evaluateFilters = evaluateFilters;
5
+ exports.evaluateFiltersIterable = evaluateFiltersIterable;
6
+ const template_engine_1 = require("@hostwebhook/template-engine");
7
+ const regex_de_inquilino_1 = require("./regex-de-inquilino");
8
+ function getNestedValue(obj, path) {
9
+ return path.split('.').reduce((curr, key) => {
10
+ if (curr !== null && typeof curr === 'object') {
11
+ return curr[key];
12
+ }
13
+ return undefined;
14
+ }, obj);
15
+ }
16
+ /** Try to compare as numbers; if either is NaN, try as dates; if still NaN, compare as strings. */
17
+ function smartCompare(a, b) {
18
+ const numA = Number(a);
19
+ const numB = Number(b ?? 0);
20
+ if (!isNaN(numA) && !isNaN(numB))
21
+ return { left: numA, right: numB };
22
+ // Try date comparison (ISO strings, date strings)
23
+ const dA = new Date(String(a)).getTime();
24
+ const dB = new Date(String(b)).getTime();
25
+ if (!isNaN(dA) && !isNaN(dB))
26
+ return { left: dA, right: dB };
27
+ // Fallback: lexicographic string comparison via charCodeAt sum won't work — use localeCompare
28
+ const cmp = String(a).localeCompare(String(b));
29
+ return { left: cmp, right: 0 };
30
+ }
31
+ function matchFilter(f, payload, opts) {
32
+ // Resolve field: supports {{$now}}, {{payload.field}}, or plain dot notation
33
+ const fieldStr = String(f.field ?? '');
34
+ const val = /^\{\{/.test(fieldStr)
35
+ ? (0, template_engine_1.resolveInput)(fieldStr, payload, opts)
36
+ : getNestedValue(payload, (0, template_engine_1.resolveFieldPath)(fieldStr));
37
+ const fv = (0, template_engine_1.resolveInput)(f.value, payload, opts);
38
+ switch (f.operator) {
39
+ // String
40
+ case 'eq':
41
+ return String(val) === String(fv ?? '');
42
+ case 'neq':
43
+ return String(val) !== String(fv ?? '');
44
+ case 'contains':
45
+ return typeof val === 'string' && val.includes(String(fv ?? ''));
46
+ case 'not_contains':
47
+ return typeof val === 'string' && !val.includes(String(fv ?? ''));
48
+ case 'starts_with':
49
+ return typeof val === 'string' && val.startsWith(String(fv ?? ''));
50
+ case 'not_starts_with':
51
+ return typeof val === 'string' && !val.startsWith(String(fv ?? ''));
52
+ case 'ends_with':
53
+ return typeof val === 'string' && val.endsWith(String(fv ?? ''));
54
+ case 'not_ends_with':
55
+ return typeof val === 'string' && !val.endsWith(String(fv ?? ''));
56
+ case 'is_empty':
57
+ return val === undefined || val === null || val === '';
58
+ case 'is_not_empty':
59
+ return val !== undefined && val !== null && val !== '';
60
+ case 'matches_regex':
61
+ /* El patron lo escribe el inquilino y la cadena viene del payload, que
62
+ entra por el ingress publico: con el motor de JavaScript, `(a+)+$`
63
+ contra treinta caracteres deja el hilo girando. Ver
64
+ `regex-de-inquilino.ts`. */
65
+ return (0, regex_de_inquilino_1.casaRegexDeInquilino)(String(fv ?? ''), val, 'matches_regex');
66
+ // Number / Date (smart comparison)
67
+ case 'gt': {
68
+ const c = smartCompare(val, fv);
69
+ return c.left > c.right;
70
+ }
71
+ case 'gte': {
72
+ const c = smartCompare(val, fv);
73
+ return c.left >= c.right;
74
+ }
75
+ case 'lt': {
76
+ const c = smartCompare(val, fv);
77
+ return c.left < c.right;
78
+ }
79
+ case 'lte': {
80
+ const c = smartCompare(val, fv);
81
+ return c.left <= c.right;
82
+ }
83
+ // Date comparison
84
+ case 'date_before': {
85
+ const dA = new Date(String(val)).getTime();
86
+ const dB = new Date(String(fv)).getTime();
87
+ return !isNaN(dA) && !isNaN(dB) && dA < dB;
88
+ }
89
+ case 'date_after': {
90
+ const dA = new Date(String(val)).getTime();
91
+ const dB = new Date(String(fv)).getTime();
92
+ return !isNaN(dA) && !isNaN(dB) && dA > dB;
93
+ }
94
+ case 'date_eq': {
95
+ const dA = new Date(String(val)).toISOString().slice(0, 10);
96
+ const dB = new Date(String(fv)).toISOString().slice(0, 10);
97
+ return dA === dB;
98
+ }
99
+ // Boolean
100
+ case 'is_true':
101
+ return val === true || val === 'true' || val === 1;
102
+ case 'is_false':
103
+ return val === false || val === 'false' || val === 0;
104
+ // Existence
105
+ case 'exists':
106
+ return val !== undefined && val !== null;
107
+ case 'not_exists':
108
+ return val === undefined || val === null;
109
+ // Array
110
+ case 'array_contains':
111
+ return (Array.isArray(val) && val.some((v) => String(v) === String(fv ?? '')));
112
+ case 'array_not_contains':
113
+ return (Array.isArray(val) && !val.some((v) => String(v) === String(fv ?? '')));
114
+ case 'array_empty':
115
+ return Array.isArray(val) && val.length === 0;
116
+ case 'array_not_empty':
117
+ return Array.isArray(val) && val.length > 0;
118
+ case 'array_length_eq':
119
+ return Array.isArray(val) && val.length === Number(fv ?? 0);
120
+ case 'array_length_gt':
121
+ return Array.isArray(val) && val.length > Number(fv ?? 0);
122
+ case 'array_length_lt':
123
+ return Array.isArray(val) && val.length < Number(fv ?? 0);
124
+ // Object
125
+ case 'has_key':
126
+ return (val !== null &&
127
+ typeof val === 'object' &&
128
+ !Array.isArray(val) &&
129
+ String(fv ?? '') in val);
130
+ default:
131
+ return true;
132
+ }
133
+ }
134
+ function evaluateFilters(filters, payload, mode = 'and', opts) {
135
+ const active = (filters ?? []).filter((f) => f.enabled !== false);
136
+ if (active.length === 0)
137
+ return true;
138
+ if (mode === 'and')
139
+ return active.every((f) => matchFilter(f, payload, opts));
140
+ return active.some((f) => matchFilter(f, payload, opts));
141
+ }
142
+ /**
143
+ * Evaluate filters against a payload that may be iterable.
144
+ * If iterable: filters each item, returns a new iterable payload with only passing items.
145
+ * If not iterable: returns { passed, payload } with the original payload.
146
+ */
147
+ function evaluateFiltersIterable(filters, payload, mode = 'and', outputAsSingle = false, opts) {
148
+ const meta = payload._meta;
149
+ if (meta?.iterable && meta.iterateField) {
150
+ const items = payload[meta.iterateField] ?? [];
151
+ const passing = items.filter((item) => evaluateFilters(filters, item, mode, opts));
152
+ const resultPayload = outputAsSingle
153
+ ? { _meta: { iterable: false, count: 1 }, [meta.iterateField]: passing }
154
+ : {
155
+ _meta: {
156
+ iterable: true,
157
+ iterateField: meta.iterateField,
158
+ count: passing.length,
159
+ },
160
+ [meta.iterateField]: passing,
161
+ };
162
+ return {
163
+ passed: passing.length > 0,
164
+ resultPayload,
165
+ totalItems: items.length,
166
+ passedItems: passing.length,
167
+ };
168
+ }
169
+ // Strip _meta for evaluation, keep it in result
170
+ const { _meta, ...cleanPayload } = payload;
171
+ const passed = evaluateFilters(filters, cleanPayload, mode, opts);
172
+ return {
173
+ passed,
174
+ resultPayload: payload,
175
+ totalItems: 1,
176
+ passedItems: passed ? 1 : 0,
177
+ };
178
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shared helpers for node handler definitions.
3
+ * Reusable getOutputPayload, buildLastPayload, and buildDeliveryRecord builders
4
+ * that most action/processing handlers share.
5
+ */
6
+ type Result = {
7
+ statusCode: number;
8
+ responseBody: string;
9
+ latencyMs: number;
10
+ outputPayload?: Record<string, unknown>;
11
+ };
12
+ /** Standard getOutputPayload — parse responseBody, preserve _meta, spread fields */
13
+ export declare function standardGetOutputPayload(_entity: any, result: Result, _input: Record<string, unknown>): Record<string, unknown> | null;
14
+ /** Standard buildLastPayload — wrap output with success/statusCode/latencyMs in _meta */
15
+ export declare function standardBuildLastPayload(_entity: any, result: Result, output: Record<string, unknown>): Record<string, unknown>;
16
+ /** Build a standard action delivery record */
17
+ export declare function buildActionDeliveryRecord(actionIdField: string, entity: any, result: Result, success: boolean, ctx: {
18
+ event: any;
19
+ webhook: any;
20
+ }, targetUrl?: string): Record<string, unknown>;
21
+ export {};
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /**
3
+ * Shared helpers for node handler definitions.
4
+ * Reusable getOutputPayload, buildLastPayload, and buildDeliveryRecord builders
5
+ * that most action/processing handlers share.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.standardGetOutputPayload = standardGetOutputPayload;
9
+ exports.standardBuildLastPayload = standardBuildLastPayload;
10
+ exports.buildActionDeliveryRecord = buildActionDeliveryRecord;
11
+ /** Standard getOutputPayload — parse responseBody, preserve _meta, spread fields */
12
+ function standardGetOutputPayload(_entity, result, _input) {
13
+ try {
14
+ const parsed = result.responseBody ? JSON.parse(result.responseBody) : {};
15
+ const meta = parsed?._meta ?? {};
16
+ return {
17
+ _meta: { iterable: false, count: 1, ...meta },
18
+ ...Object.fromEntries(Object.entries(typeof parsed === 'object' && parsed !== null
19
+ ? parsed
20
+ : { value: parsed }).filter(([k]) => k !== '_meta')),
21
+ };
22
+ }
23
+ catch {
24
+ return { _meta: { iterable: false, count: 1 }, raw: result.responseBody };
25
+ }
26
+ }
27
+ /** Standard buildLastPayload — wrap output with success/statusCode/latencyMs in _meta */
28
+ function standardBuildLastPayload(_entity, result, output) {
29
+ const meta = output._meta ?? {};
30
+ return {
31
+ _meta: {
32
+ ...meta,
33
+ success: result.statusCode < 400,
34
+ statusCode: result.statusCode,
35
+ latencyMs: result.latencyMs,
36
+ },
37
+ ...Object.fromEntries(Object.entries(output).filter(([k]) => k !== '_meta')),
38
+ };
39
+ }
40
+ /** Build a standard action delivery record */
41
+ function buildActionDeliveryRecord(actionIdField, entity, result, success, ctx, targetUrl) {
42
+ return {
43
+ eventId: ctx.event.id,
44
+ webhookId: ctx.webhook.id,
45
+ [actionIdField]: entity._id?.toString(),
46
+ statusCode: result.statusCode || (success ? 200 : 0),
47
+ success,
48
+ error: success ? undefined : result.responseBody,
49
+ targetUrl: targetUrl ?? '',
50
+ attemptedAt: new Date(),
51
+ attempt: 1,
52
+ };
53
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @hostwebhook/node-sdk
3
+ *
4
+ * Lo que un servicio necesita para EJECUTAR nodos de HostWebhook, sin traerse
5
+ * la aplicación entera. Sale de `api/src/common/`, donde estos 27 ficheros
6
+ * convivían con el motor de ejecución y con los servicios de operaciones de
7
+ * cada integración.
8
+ *
9
+ * ## Qué NO está aquí, y por qué
10
+ *
11
+ * - **`BaseNodeService`** y el **factory de controladores**. Arrastran 19 y
12
+ * 37 ficheros de fuera —planes, credenciales, auditoría, decoradores de
13
+ * auth— por cinco y cuatro colaboradores concretos. Salen cuando esos se
14
+ * inviertan tras una interfaz, no antes.
15
+ * - **El motor** (`event-pipeline`, `pipeline-run`). Es la aplicación, no
16
+ * el SDK: decide QUÉ nodo corre y cuándo. Aquí está sólo el CÓMO corre uno.
17
+ * - Los **servicios de operaciones** por integración. Se van con los nodos.
18
+ *
19
+ * ## Los contratos
20
+ *
21
+ * `contratos.ts` declara los siete tipos que antes llegaban de entidades de
22
+ * la api por `import type`. La dirección se invierte: los define el SDK y la
23
+ * api los cumple.
24
+ */
25
+ export * from "./code-runner";
26
+ export * from "./contratos";
27
+ export * from "./ensure-meta";
28
+ export * from "./execute-with-iteration";
29
+ export * from "./filter-utils";
30
+ export * from "./handler-helpers";
31
+ export * from "./log-metadata";
32
+ export * from "./node-dispatch.registry";
33
+ export * from "./node-executors";
34
+ export * from "./node-lifecycle";
35
+ export * from "./normalize-nodes";
36
+ export * from "./output-node-ref.schema";
37
+ export * from "./output-webhook-scope";
38
+ export * from "./payload-preview";
39
+ export * from "./pipeline.constants";
40
+ export * from "./pre-request-pool";
41
+ export * from "./pre-request-runner-source";
42
+ export * from "./regex-de-inquilino";
43
+ export * from "./request-context";
44
+ export { retryOnTransientError, type NodeResult } from "./retry-transient";
45
+ export * from "./retry-utils";
46
+ export * from "./schema-validator-utils";
47
+ export * from "./ssrf-guard";
48
+ export * from "./swallow";
49
+ export * from "./template-render";
50
+ export * from "./try-parse";
51
+ export * from "./workspace-payloads";
package/dist/index.js ADDED
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.retryOnTransientError = void 0;
18
+ /**
19
+ * @hostwebhook/node-sdk
20
+ *
21
+ * Lo que un servicio necesita para EJECUTAR nodos de HostWebhook, sin traerse
22
+ * la aplicación entera. Sale de `api/src/common/`, donde estos 27 ficheros
23
+ * convivían con el motor de ejecución y con los servicios de operaciones de
24
+ * cada integración.
25
+ *
26
+ * ## Qué NO está aquí, y por qué
27
+ *
28
+ * - **`BaseNodeService`** y el **factory de controladores**. Arrastran 19 y
29
+ * 37 ficheros de fuera —planes, credenciales, auditoría, decoradores de
30
+ * auth— por cinco y cuatro colaboradores concretos. Salen cuando esos se
31
+ * inviertan tras una interfaz, no antes.
32
+ * - **El motor** (`event-pipeline`, `pipeline-run`). Es la aplicación, no
33
+ * el SDK: decide QUÉ nodo corre y cuándo. Aquí está sólo el CÓMO corre uno.
34
+ * - Los **servicios de operaciones** por integración. Se van con los nodos.
35
+ *
36
+ * ## Los contratos
37
+ *
38
+ * `contratos.ts` declara los siete tipos que antes llegaban de entidades de
39
+ * la api por `import type`. La dirección se invierte: los define el SDK y la
40
+ * api los cumple.
41
+ */
42
+ __exportStar(require("./code-runner"), exports);
43
+ __exportStar(require("./contratos"), exports);
44
+ __exportStar(require("./ensure-meta"), exports);
45
+ __exportStar(require("./execute-with-iteration"), exports);
46
+ __exportStar(require("./filter-utils"), exports);
47
+ __exportStar(require("./handler-helpers"), exports);
48
+ __exportStar(require("./log-metadata"), exports);
49
+ __exportStar(require("./node-dispatch.registry"), exports);
50
+ __exportStar(require("./node-executors"), exports);
51
+ __exportStar(require("./node-lifecycle"), exports);
52
+ __exportStar(require("./normalize-nodes"), exports);
53
+ __exportStar(require("./output-node-ref.schema"), exports);
54
+ __exportStar(require("./output-webhook-scope"), exports);
55
+ __exportStar(require("./payload-preview"), exports);
56
+ __exportStar(require("./pipeline.constants"), exports);
57
+ __exportStar(require("./pre-request-pool"), exports);
58
+ __exportStar(require("./pre-request-runner-source"), exports);
59
+ __exportStar(require("./regex-de-inquilino"), exports);
60
+ __exportStar(require("./request-context"), exports);
61
+ /* ⚠️ `retry-transient` y `retry-utils` declaran cada uno su `RetryOptions`,
62
+ y NO son el mismo tipo. El barril reexporta el de `retry-utils` —el
63
+ general— y del otro saca sólo sus funciones; quien necesite ese tipo lo
64
+ importa por su módulo. Unificarlos es trabajo de la api, no del paquete. */
65
+ var retry_transient_1 = require("./retry-transient");
66
+ Object.defineProperty(exports, "retryOnTransientError", { enumerable: true, get: function () { return retry_transient_1.retryOnTransientError; } });
67
+ __exportStar(require("./retry-utils"), exports);
68
+ __exportStar(require("./schema-validator-utils"), exports);
69
+ __exportStar(require("./ssrf-guard"), exports);
70
+ __exportStar(require("./swallow"), exports);
71
+ __exportStar(require("./template-render"), exports);
72
+ __exportStar(require("./try-parse"), exports);
73
+ __exportStar(require("./workspace-payloads"), exports);
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Lo que un log de un nodo guarda, y con qué nombre.
3
+ *
4
+ * ## El desorden que esto viene a cerrar
5
+ *
6
+ * Auditado el 2026-08-27 sobre los cinco sitios que escriben `metadata`:
7
+ *
8
+ * | camino | entrada | salida | error |
9
+ * |-----------------------|---------------------|----------------------|--------------------------------|
10
+ * | producción (C1) | **ninguna** | `responseBody` ≤4096 | `error` = COPIA del response |
11
+ * | Run Test (C3) | `payloadPreview` | **ninguna** | `error` = el response |
12
+ * | Run Pipeline (C2) | **ninguna** | `responseBody` ≤1024 | `error` = el response, entero |
13
+ * | entrega ok | `requestPayload` | `responseBody` ≤2048 | — |
14
+ * | entrega fallida | `requestPayload` | `responseBody` ≤1024 | `error` = mensaje de verdad |
15
+ *
16
+ * Tres defectos, y los tres se ven en pantalla:
17
+ *
18
+ * 1. **«Error» casi nunca es un error.** En tres de los cinco sitios es el
19
+ * response copiado tal cual. La pantalla pinta dos paneles con el mismo
20
+ * contenido y titula uno «ERROR», así que el usuario busca ahí la causa y
21
+ * encuentra otra vez la salida.
22
+ * 2. **La entrada no se guarda donde más se necesita.** En producción no se
23
+ * guarda ninguna: con un nodo que falló, el log no dice con qué datos
24
+ * falló.
25
+ * 3. **El recorte rompe el JSON.** `truncatePreview` devuelve un OBJETO si
26
+ * cabe y una CADENA CORTADA si no. El mismo campo cambia de tipo según el
27
+ * tamaño, así que el visor pinta un árbol o un churro de texto sin que
28
+ * nada lo anuncie. Medido con un caso real: un response de 1270 caracteres
29
+ * pasaba entero por el `error` (sin límite) y cortado por el
30
+ * `responseBody` (límite 1024) — los dos paneles, el mismo dato, uno
31
+ * legible y el otro no.
32
+ *
33
+ * ## Lo que se guarda ahora
34
+ *
35
+ * Dos campos con el mismo significado en todos los caminos, más una frase:
36
+ *
37
+ * - `payload` — lo que ENTRÓ al nodo.
38
+ * - `response` — lo que SALIÓ. Al fallar lleva dentro un `error` con la
39
+ * frase que lo explica, en el hueco que deja el payload
40
+ * repetido.
41
+ *
42
+ * La frase estuvo un rato en un campo aparte (`errorSummary`) que la pantalla
43
+ * pintaba en una caja roja encima del panel. Se metió dentro porque lo que se
44
+ * quiere leer de un log que falló es «qué contestó el nodo», y eso son el
45
+ * veredicto y el motivo juntos — no uno arriba y otro dentro.
46
+ *
47
+ * Las claves viejas se siguen escribiendo un tiempo (`responseBody`,
48
+ * `payloadPreview`, `requestPayload`) porque los logs ya guardados las llevan
49
+ * y la pantalla tiene que poder pintar el historial. Ver
50
+ * `LEER_TAMBIEN_LAS_VIEJAS` abajo.
51
+ */
52
+ /** Tope por defecto. 16 KB: 16 veces el peor límite de antes, y de sobra para
53
+ * cualquier payload real —el caso que destapó esto medía 1,2 KB—. Existe un
54
+ * tope porque un documento de Mongo no pasa de 16 MB, no por ahorrar. */
55
+ export declare const TOPE_DE_LOG: number;
56
+ /**
57
+ * Recorta SIN romper la forma.
58
+ *
59
+ * `truncatePreview` corta la cadena serializada por la mitad y le pega
60
+ * `...[truncated]`. Eso deja un JSON inválido, y el panel que lo recibe deja
61
+ * de poder plegarlo, colorearlo o buscar dentro: pasa a ser texto.
62
+ *
63
+ * Aquí se recorta por DENTRO: se sustituye el valor grande por una nota que
64
+ * dice qué había, y lo que sale sigue siendo un objeto válido. Un campo que
65
+ * no cabe se lee como «aquí había 40 KB de texto» en vez de dejar el
66
+ * documento entero ilegible por culpa de ese campo.
67
+ */
68
+ export declare function recortarEstructural(value: unknown, tope?: number): unknown;
69
+ /**
70
+ * Lo que se pinta en lugar del dato repetido. En inglés: acaba en pantalla.
71
+ *
72
+ * ⚠️ NO empieza por `[` ni por `{`, y no es un capricho: cuando lo que se
73
+ * sustituye es el response ENTERO, esta cadena llega sola al panel, y el
74
+ * dashboard decide si pintar el visor de JSON mirando si el texto empieza por
75
+ * `[` o `{` (`looksLikeJson` en `app/dashboard/logs/page.tsx`). Con corchetes
76
+ * intentaría parsear «[identical to…]» como JSON y no es JSON.
77
+ */
78
+ export declare const IGUAL_QUE_LA_ENTRADA = "Same as the payload below";
79
+ /**
80
+ * ¿Estos dos llevan el mismo contenido?
81
+ *
82
+ * `_meta` se ignora a propósito: es contabilidad que el pipeline añade
83
+ * (`iterable`, `count`), no dato del usuario. Un filtro que deja pasar el
84
+ * payload tal cual devuelve el MISMO objeto pero con `_meta` puesto, así que
85
+ * comparar en crudo diría «distintos» justo en el caso que esto viene a
86
+ * detectar.
87
+ *
88
+ * Comparar por JSON con las claves ordenadas y no con `===`: el response viaja
89
+ * serializado y vuelve parseado, así que nunca es el mismo objeto en memoria
90
+ * aunque sea el mismo dato.
91
+ */
92
+ export declare function mismoContenido(a: unknown, b: unknown): boolean;
93
+ /**
94
+ * Quita del response lo que YA se guarda como entrada.
95
+ *
96
+ * ## Por qué
97
+ *
98
+ * Un log pinta el response arriba y el payload abajo, y en los nodos que
99
+ * dejan pasar los datos eso es el MISMO objeto dos veces. En un filtro no
100
+ * iterable es literal: `evaluateFiltersIterable` devuelve `resultPayload:
101
+ * payload`, el propio objeto de entrada. Reportado mirando un log: «si arriba
102
+ * está la response y abajo el payload, estaríamos repitiendo data».
103
+ *
104
+ * ## Por qué no se arregla en el ejecutor
105
+ *
106
+ * Porque `responseBody` **no es un campo de log**: se lee en 140 sitios de 46
107
+ * ficheros y una veintena lo parsean para DECIDIR. `standardGetOutputPayload`
108
+ * —que usan 23 handlers— construye parseándolo el payload que va aguas abajo;
109
+ * `pipeline-run` y `test-node` caen a `JSON.parse(responseBody)` cuando el
110
+ * handler no transforma; `cache` lee `.action`, `conditional` `.matchedId`,
111
+ * `schemaValidator` `.valid`. Vaciar el response ahí es cambiar la ejecución.
112
+ *
113
+ * El log, en cambio, es una hoja: nadie lee hacia atrás desde él. Así que la
114
+ * poda vive aquí.
115
+ *
116
+ * ## La regla
117
+ *
118
+ * Sólo se quita lo que está DEMOSTRADO repetido, comparando contenido:
119
+ *
120
+ * - el response entero igual a la entrada → se sustituye por la nota;
121
+ * - `response.payload` igual a la entrada → se sustituye ese campo y el
122
+ * veredicto (`passed`, `totalItems`…) se queda;
123
+ * - distintos → intacto.
124
+ *
125
+ * Ese último caso no es teórico: con un payload iterable el filtro devuelve un
126
+ * objeto NUEVO con sólo los items que pasaron. Ahí `response.payload` no es la
127
+ * entrada, es el resultado del filtrado, y borrarlo perdería el dato que más
128
+ * interesa.
129
+ *
130
+ * Y se sustituye en vez de borrar: un campo que desaparece se lee como «el
131
+ * nodo no devolvió nada», que es otra cosa.
132
+ */
133
+ export declare function podarLoRepetido(response: unknown, entrada: unknown,
134
+ /**
135
+ * La frase del fallo, si el nodo falló.
136
+ *
137
+ * Va DENTRO del response, en el hueco que deja el payload repetido, en vez
138
+ * de aparte y en rojo encima del panel. Así el panel de arriba se lee
139
+ * entero como «qué contestó el nodo»: el veredicto y por qué, juntos, que
140
+ * es lo que se quiere saber al abrir un log que falló.
141
+ */
142
+ fallo?: string): unknown;
143
+ /** El response ya parseado, si es que se puede. Nunca revienta. */
144
+ export declare function parsearResponse(responseBody: unknown): unknown;
145
+ /**
146
+ * Qué falló, en una frase.
147
+ *
148
+ * En inglés porque es texto de pantalla, y la pantalla está en inglés — hay
149
+ * un guardián en el dashboard (`check-spanish-ui`) que lo exige.
150
+ *
151
+ * El objetivo NO es reemplazar al response: es que la primera línea diga qué
152
+ * pasó sin tener que leer JSON. El response sigue guardado al lado.
153
+ */
154
+ export declare function explicarFallo(args: {
155
+ nodeType?: string;
156
+ nodeName?: string;
157
+ statusCode?: number;
158
+ response?: unknown;
159
+ }): string;
160
+ /**
161
+ * Las claves de antes, que la PANTALLA sigue teniendo que leer.
162
+ *
163
+ * No se borran de los logs ya escritos, así que el dashboard mira primero la
164
+ * nueva y luego estas. Se listan aquí para que el día que se dejen de
165
+ * escribir haya un sitio donde mirar cuáles eran.
166
+ */
167
+ export declare const LEER_TAMBIEN_LAS_VIEJAS: {
168
+ readonly payload: readonly ["requestPayload", "payloadPreview"];
169
+ readonly response: readonly ["responseBody"];
170
+ };
171
+ /**
172
+ * Los metadatos de un log de nodo, iguales vengan del camino que vengan.
173
+ *
174
+ * `extra` es lo que aporta cada nodo (`filterMode`, `attempt`, `targetUrl`…)
175
+ * y va primero, para que no pueda pisar a los tres campos que esta función
176
+ * garantiza.
177
+ */
178
+ export declare function construirMetadatosDeLog(args: {
179
+ nodeType?: string;
180
+ nodeName?: string;
181
+ /** Lo que entró al nodo. */
182
+ payload?: unknown;
183
+ /** Lo que salió, en crudo o ya parseado. */
184
+ responseBody?: unknown;
185
+ statusCode?: number;
186
+ success: boolean;
187
+ extra?: Record<string, unknown>;
188
+ /** Escribir además las claves viejas. Por defecto sí, mientras la pantalla
189
+ * desplegada siga siendo la anterior. */
190
+ conClavesViejas?: boolean;
191
+ }): Record<string, unknown>;