@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.
- package/dist/code-runner.d.ts +20 -0
- package/dist/code-runner.js +138 -0
- package/dist/contratos.d.ts +121 -0
- package/dist/contratos.js +24 -0
- package/dist/dto/output-node.dto.d.ts +19 -0
- package/dist/dto/output-node.dto.js +96 -0
- package/dist/ensure-meta.d.ts +22 -0
- package/dist/ensure-meta.js +35 -0
- package/dist/execute-with-iteration.d.ts +18 -0
- package/dist/execute-with-iteration.js +66 -0
- package/dist/filter-utils.d.ts +22 -0
- package/dist/filter-utils.js +178 -0
- package/dist/handler-helpers.d.ts +21 -0
- package/dist/handler-helpers.js +53 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +73 -0
- package/dist/log-metadata.d.ts +191 -0
- package/dist/log-metadata.js +375 -0
- package/dist/node-dispatch.registry.d.ts +32 -0
- package/dist/node-dispatch.registry.js +45 -0
- package/dist/node-executors.d.ts +299 -0
- package/dist/node-executors.js +555 -0
- package/dist/node-lifecycle.d.ts +399 -0
- package/dist/node-lifecycle.js +782 -0
- package/dist/normalize-nodes.d.ts +18 -0
- package/dist/normalize-nodes.js +22 -0
- package/dist/output-node-ref.schema.d.ts +82 -0
- package/dist/output-node-ref.schema.js +90 -0
- package/dist/output-webhook-scope.d.ts +36 -0
- package/dist/output-webhook-scope.js +42 -0
- package/dist/payload-preview.d.ts +10 -0
- package/dist/payload-preview.js +39 -0
- package/dist/pipeline.constants.d.ts +29 -0
- package/dist/pipeline.constants.js +51 -0
- package/dist/pre-request-pool.d.ts +58 -0
- package/dist/pre-request-pool.js +308 -0
- package/dist/pre-request-runner-source.d.ts +28 -0
- package/dist/pre-request-runner-source.js +411 -0
- package/dist/regex-de-inquilino.d.ts +15 -0
- package/dist/regex-de-inquilino.js +98 -0
- package/dist/request-context.d.ts +18 -0
- package/dist/request-context.js +34 -0
- package/dist/retry-transient.d.ts +54 -0
- package/dist/retry-transient.js +67 -0
- package/dist/retry-utils.d.ts +17 -0
- package/dist/retry-utils.js +23 -0
- package/dist/schema-validator-utils.d.ts +9 -0
- package/dist/schema-validator-utils.js +140 -0
- package/dist/ssrf-guard.d.ts +202 -0
- package/dist/ssrf-guard.js +917 -0
- package/dist/swallow.d.ts +52 -0
- package/dist/swallow.js +55 -0
- package/dist/template-render.d.ts +33 -0
- package/dist/template-render.js +43 -0
- package/dist/try-parse.d.ts +41 -0
- package/dist/try-parse.js +69 -0
- package/dist/workspace-payloads.d.ts +66 -0
- package/dist/workspace-payloads.js +496 -0
- package/package.json +35 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Lo que un log de un nodo guarda, y con qué nombre.
|
|
4
|
+
*
|
|
5
|
+
* ## El desorden que esto viene a cerrar
|
|
6
|
+
*
|
|
7
|
+
* Auditado el 2026-08-27 sobre los cinco sitios que escriben `metadata`:
|
|
8
|
+
*
|
|
9
|
+
* | camino | entrada | salida | error |
|
|
10
|
+
* |-----------------------|---------------------|----------------------|--------------------------------|
|
|
11
|
+
* | producción (C1) | **ninguna** | `responseBody` ≤4096 | `error` = COPIA del response |
|
|
12
|
+
* | Run Test (C3) | `payloadPreview` | **ninguna** | `error` = el response |
|
|
13
|
+
* | Run Pipeline (C2) | **ninguna** | `responseBody` ≤1024 | `error` = el response, entero |
|
|
14
|
+
* | entrega ok | `requestPayload` | `responseBody` ≤2048 | — |
|
|
15
|
+
* | entrega fallida | `requestPayload` | `responseBody` ≤1024 | `error` = mensaje de verdad |
|
|
16
|
+
*
|
|
17
|
+
* Tres defectos, y los tres se ven en pantalla:
|
|
18
|
+
*
|
|
19
|
+
* 1. **«Error» casi nunca es un error.** En tres de los cinco sitios es el
|
|
20
|
+
* response copiado tal cual. La pantalla pinta dos paneles con el mismo
|
|
21
|
+
* contenido y titula uno «ERROR», así que el usuario busca ahí la causa y
|
|
22
|
+
* encuentra otra vez la salida.
|
|
23
|
+
* 2. **La entrada no se guarda donde más se necesita.** En producción no se
|
|
24
|
+
* guarda ninguna: con un nodo que falló, el log no dice con qué datos
|
|
25
|
+
* falló.
|
|
26
|
+
* 3. **El recorte rompe el JSON.** `truncatePreview` devuelve un OBJETO si
|
|
27
|
+
* cabe y una CADENA CORTADA si no. El mismo campo cambia de tipo según el
|
|
28
|
+
* tamaño, así que el visor pinta un árbol o un churro de texto sin que
|
|
29
|
+
* nada lo anuncie. Medido con un caso real: un response de 1270 caracteres
|
|
30
|
+
* pasaba entero por el `error` (sin límite) y cortado por el
|
|
31
|
+
* `responseBody` (límite 1024) — los dos paneles, el mismo dato, uno
|
|
32
|
+
* legible y el otro no.
|
|
33
|
+
*
|
|
34
|
+
* ## Lo que se guarda ahora
|
|
35
|
+
*
|
|
36
|
+
* Dos campos con el mismo significado en todos los caminos, más una frase:
|
|
37
|
+
*
|
|
38
|
+
* - `payload` — lo que ENTRÓ al nodo.
|
|
39
|
+
* - `response` — lo que SALIÓ. Al fallar lleva dentro un `error` con la
|
|
40
|
+
* frase que lo explica, en el hueco que deja el payload
|
|
41
|
+
* repetido.
|
|
42
|
+
*
|
|
43
|
+
* La frase estuvo un rato en un campo aparte (`errorSummary`) que la pantalla
|
|
44
|
+
* pintaba en una caja roja encima del panel. Se metió dentro porque lo que se
|
|
45
|
+
* quiere leer de un log que falló es «qué contestó el nodo», y eso son el
|
|
46
|
+
* veredicto y el motivo juntos — no uno arriba y otro dentro.
|
|
47
|
+
*
|
|
48
|
+
* Las claves viejas se siguen escribiendo un tiempo (`responseBody`,
|
|
49
|
+
* `payloadPreview`, `requestPayload`) porque los logs ya guardados las llevan
|
|
50
|
+
* y la pantalla tiene que poder pintar el historial. Ver
|
|
51
|
+
* `LEER_TAMBIEN_LAS_VIEJAS` abajo.
|
|
52
|
+
*/
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.LEER_TAMBIEN_LAS_VIEJAS = exports.IGUAL_QUE_LA_ENTRADA = exports.TOPE_DE_LOG = void 0;
|
|
55
|
+
exports.recortarEstructural = recortarEstructural;
|
|
56
|
+
exports.mismoContenido = mismoContenido;
|
|
57
|
+
exports.podarLoRepetido = podarLoRepetido;
|
|
58
|
+
exports.parsearResponse = parsearResponse;
|
|
59
|
+
exports.explicarFallo = explicarFallo;
|
|
60
|
+
exports.construirMetadatosDeLog = construirMetadatosDeLog;
|
|
61
|
+
/** Tope por defecto. 16 KB: 16 veces el peor límite de antes, y de sobra para
|
|
62
|
+
* cualquier payload real —el caso que destapó esto medía 1,2 KB—. Existe un
|
|
63
|
+
* tope porque un documento de Mongo no pasa de 16 MB, no por ahorrar. */
|
|
64
|
+
exports.TOPE_DE_LOG = 16 * 1024;
|
|
65
|
+
/**
|
|
66
|
+
* Recorta SIN romper la forma.
|
|
67
|
+
*
|
|
68
|
+
* `truncatePreview` corta la cadena serializada por la mitad y le pega
|
|
69
|
+
* `...[truncated]`. Eso deja un JSON inválido, y el panel que lo recibe deja
|
|
70
|
+
* de poder plegarlo, colorearlo o buscar dentro: pasa a ser texto.
|
|
71
|
+
*
|
|
72
|
+
* Aquí se recorta por DENTRO: se sustituye el valor grande por una nota que
|
|
73
|
+
* dice qué había, y lo que sale sigue siendo un objeto válido. Un campo que
|
|
74
|
+
* no cabe se lee como «aquí había 40 KB de texto» en vez de dejar el
|
|
75
|
+
* documento entero ilegible por culpa de ese campo.
|
|
76
|
+
*/
|
|
77
|
+
function recortarEstructural(value, tope = exports.TOPE_DE_LOG) {
|
|
78
|
+
if (value === undefined)
|
|
79
|
+
return undefined;
|
|
80
|
+
if (value === null)
|
|
81
|
+
return null;
|
|
82
|
+
const cabe = (v) => {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.stringify(v)?.length ?? 0;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return Infinity;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
if (cabe(value) <= tope)
|
|
91
|
+
return value;
|
|
92
|
+
/* Una cadena suelta demasiado grande sí se corta —no tiene estructura que
|
|
93
|
+
preservar— pero se dice cuánto se dejó fuera, que es lo que `[truncated]`
|
|
94
|
+
a secas no decía. */
|
|
95
|
+
if (typeof value === 'string') {
|
|
96
|
+
return `${value.slice(0, tope)}\n\n[+${value.length - tope} caracteres más — recortado para el log]`;
|
|
97
|
+
}
|
|
98
|
+
if (typeof value !== 'object')
|
|
99
|
+
return value;
|
|
100
|
+
/* Se poda de mayor a menor hasta que quepa: así lo que se pierde es el
|
|
101
|
+
campo gordo que causó el problema, no los treinta pequeños que sí
|
|
102
|
+
interesan. */
|
|
103
|
+
const podar = (v, profundidad) => {
|
|
104
|
+
if (v === null || typeof v !== 'object')
|
|
105
|
+
return v;
|
|
106
|
+
if (Array.isArray(v)) {
|
|
107
|
+
if (profundidad <= 0)
|
|
108
|
+
return `[array de ${v.length} elementos — recortado]`;
|
|
109
|
+
return v.map((x) => podar(x, profundidad - 1));
|
|
110
|
+
}
|
|
111
|
+
if (profundidad <= 0) {
|
|
112
|
+
return `[objeto con ${Object.keys(v).length} campos — recortado]`;
|
|
113
|
+
}
|
|
114
|
+
const entradas = Object.entries(v);
|
|
115
|
+
const salida = {};
|
|
116
|
+
for (const [k, val] of entradas) {
|
|
117
|
+
const tam = cabe(val);
|
|
118
|
+
salida[k] =
|
|
119
|
+
typeof val === 'string' && tam > tope / 4
|
|
120
|
+
? `[${val.length} caracteres — recortado para el log]`
|
|
121
|
+
: podar(val, profundidad - 1);
|
|
122
|
+
}
|
|
123
|
+
return salida;
|
|
124
|
+
};
|
|
125
|
+
for (const profundidad of [6, 4, 3, 2, 1]) {
|
|
126
|
+
const intento = podar(value, profundidad);
|
|
127
|
+
if (cabe(intento) <= tope)
|
|
128
|
+
return intento;
|
|
129
|
+
}
|
|
130
|
+
return `[${cabe(value)} caracteres — demasiado grande para el log]`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Lo que se pinta en lugar del dato repetido. En inglés: acaba en pantalla.
|
|
134
|
+
*
|
|
135
|
+
* ⚠️ NO empieza por `[` ni por `{`, y no es un capricho: cuando lo que se
|
|
136
|
+
* sustituye es el response ENTERO, esta cadena llega sola al panel, y el
|
|
137
|
+
* dashboard decide si pintar el visor de JSON mirando si el texto empieza por
|
|
138
|
+
* `[` o `{` (`looksLikeJson` en `app/dashboard/logs/page.tsx`). Con corchetes
|
|
139
|
+
* intentaría parsear «[identical to…]» como JSON y no es JSON.
|
|
140
|
+
*/
|
|
141
|
+
exports.IGUAL_QUE_LA_ENTRADA = 'Same as the payload below';
|
|
142
|
+
/**
|
|
143
|
+
* ¿Estos dos llevan el mismo contenido?
|
|
144
|
+
*
|
|
145
|
+
* `_meta` se ignora a propósito: es contabilidad que el pipeline añade
|
|
146
|
+
* (`iterable`, `count`), no dato del usuario. Un filtro que deja pasar el
|
|
147
|
+
* payload tal cual devuelve el MISMO objeto pero con `_meta` puesto, así que
|
|
148
|
+
* comparar en crudo diría «distintos» justo en el caso que esto viene a
|
|
149
|
+
* detectar.
|
|
150
|
+
*
|
|
151
|
+
* Comparar por JSON con las claves ordenadas y no con `===`: el response viaja
|
|
152
|
+
* serializado y vuelve parseado, así que nunca es el mismo objeto en memoria
|
|
153
|
+
* aunque sea el mismo dato.
|
|
154
|
+
*/
|
|
155
|
+
function mismoContenido(a, b) {
|
|
156
|
+
if (a == null || b == null)
|
|
157
|
+
return false;
|
|
158
|
+
if (typeof a !== 'object' || typeof b !== 'object')
|
|
159
|
+
return false;
|
|
160
|
+
const normaliza = (v) => {
|
|
161
|
+
const sinMeta = Object.fromEntries(Object.entries(v).filter(([k]) => k !== '_meta'));
|
|
162
|
+
const ordenar = (x) => {
|
|
163
|
+
if (Array.isArray(x))
|
|
164
|
+
return x.map(ordenar);
|
|
165
|
+
if (x && typeof x === 'object') {
|
|
166
|
+
return Object.fromEntries(Object.entries(x)
|
|
167
|
+
.sort(([p], [q]) => p.localeCompare(q))
|
|
168
|
+
.map(([k, val]) => [k, ordenar(val)]));
|
|
169
|
+
}
|
|
170
|
+
return x;
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
return JSON.stringify(ordenar(sinMeta));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return '';
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const ja = normaliza(a);
|
|
180
|
+
return ja !== '' && ja === normaliza(b);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Quita del response lo que YA se guarda como entrada.
|
|
184
|
+
*
|
|
185
|
+
* ## Por qué
|
|
186
|
+
*
|
|
187
|
+
* Un log pinta el response arriba y el payload abajo, y en los nodos que
|
|
188
|
+
* dejan pasar los datos eso es el MISMO objeto dos veces. En un filtro no
|
|
189
|
+
* iterable es literal: `evaluateFiltersIterable` devuelve `resultPayload:
|
|
190
|
+
* payload`, el propio objeto de entrada. Reportado mirando un log: «si arriba
|
|
191
|
+
* está la response y abajo el payload, estaríamos repitiendo data».
|
|
192
|
+
*
|
|
193
|
+
* ## Por qué no se arregla en el ejecutor
|
|
194
|
+
*
|
|
195
|
+
* Porque `responseBody` **no es un campo de log**: se lee en 140 sitios de 46
|
|
196
|
+
* ficheros y una veintena lo parsean para DECIDIR. `standardGetOutputPayload`
|
|
197
|
+
* —que usan 23 handlers— construye parseándolo el payload que va aguas abajo;
|
|
198
|
+
* `pipeline-run` y `test-node` caen a `JSON.parse(responseBody)` cuando el
|
|
199
|
+
* handler no transforma; `cache` lee `.action`, `conditional` `.matchedId`,
|
|
200
|
+
* `schemaValidator` `.valid`. Vaciar el response ahí es cambiar la ejecución.
|
|
201
|
+
*
|
|
202
|
+
* El log, en cambio, es una hoja: nadie lee hacia atrás desde él. Así que la
|
|
203
|
+
* poda vive aquí.
|
|
204
|
+
*
|
|
205
|
+
* ## La regla
|
|
206
|
+
*
|
|
207
|
+
* Sólo se quita lo que está DEMOSTRADO repetido, comparando contenido:
|
|
208
|
+
*
|
|
209
|
+
* - el response entero igual a la entrada → se sustituye por la nota;
|
|
210
|
+
* - `response.payload` igual a la entrada → se sustituye ese campo y el
|
|
211
|
+
* veredicto (`passed`, `totalItems`…) se queda;
|
|
212
|
+
* - distintos → intacto.
|
|
213
|
+
*
|
|
214
|
+
* Ese último caso no es teórico: con un payload iterable el filtro devuelve un
|
|
215
|
+
* objeto NUEVO con sólo los items que pasaron. Ahí `response.payload` no es la
|
|
216
|
+
* entrada, es el resultado del filtrado, y borrarlo perdería el dato que más
|
|
217
|
+
* interesa.
|
|
218
|
+
*
|
|
219
|
+
* Y se sustituye en vez de borrar: un campo que desaparece se lee como «el
|
|
220
|
+
* nodo no devolvió nada», que es otra cosa.
|
|
221
|
+
*/
|
|
222
|
+
function podarLoRepetido(response, entrada,
|
|
223
|
+
/**
|
|
224
|
+
* La frase del fallo, si el nodo falló.
|
|
225
|
+
*
|
|
226
|
+
* Va DENTRO del response, en el hueco que deja el payload repetido, en vez
|
|
227
|
+
* de aparte y en rojo encima del panel. Así el panel de arriba se lee
|
|
228
|
+
* entero como «qué contestó el nodo»: el veredicto y por qué, juntos, que
|
|
229
|
+
* es lo que se quiere saber al abrir un log que falló.
|
|
230
|
+
*/
|
|
231
|
+
fallo) {
|
|
232
|
+
if (response == null)
|
|
233
|
+
return fallo ? { error: fallo } : response;
|
|
234
|
+
const conFallo = (r) => fallo ? { ...r, error: fallo } : r;
|
|
235
|
+
if (entrada == null) {
|
|
236
|
+
return typeof response === 'object' && !Array.isArray(response)
|
|
237
|
+
? conFallo(response)
|
|
238
|
+
: response;
|
|
239
|
+
}
|
|
240
|
+
/* El response ENTERO era la entrada. Al fallar no se pierde nada por
|
|
241
|
+
sustituirlo: lo que aporta es la frase, y el dato está justo debajo. */
|
|
242
|
+
if (mismoContenido(response, entrada)) {
|
|
243
|
+
return fallo ? { error: fallo } : exports.IGUAL_QUE_LA_ENTRADA;
|
|
244
|
+
}
|
|
245
|
+
if (typeof response !== 'object' || Array.isArray(response))
|
|
246
|
+
return response;
|
|
247
|
+
const r = response;
|
|
248
|
+
if (!('payload' in r) || !mismoContenido(r.payload, entrada)) {
|
|
249
|
+
return conFallo(r);
|
|
250
|
+
}
|
|
251
|
+
/* El payload repetido se va, y en su sitio queda la frase. Sin fallo —un
|
|
252
|
+
nodo que fue bien— no hay frase que poner, así que se queda la nota
|
|
253
|
+
diciendo dónde está el dato. */
|
|
254
|
+
if (fallo) {
|
|
255
|
+
const { payload: _repetido, ...resto } = r;
|
|
256
|
+
return { ...resto, error: fallo };
|
|
257
|
+
}
|
|
258
|
+
return { ...r, payload: exports.IGUAL_QUE_LA_ENTRADA };
|
|
259
|
+
}
|
|
260
|
+
/** El response ya parseado, si es que se puede. Nunca revienta. */
|
|
261
|
+
function parsearResponse(responseBody) {
|
|
262
|
+
if (responseBody == null)
|
|
263
|
+
return undefined;
|
|
264
|
+
if (typeof responseBody !== 'string')
|
|
265
|
+
return responseBody;
|
|
266
|
+
try {
|
|
267
|
+
return JSON.parse(responseBody);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
return responseBody;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Qué falló, en una frase.
|
|
275
|
+
*
|
|
276
|
+
* En inglés porque es texto de pantalla, y la pantalla está en inglés — hay
|
|
277
|
+
* un guardián en el dashboard (`check-spanish-ui`) que lo exige.
|
|
278
|
+
*
|
|
279
|
+
* El objetivo NO es reemplazar al response: es que la primera línea diga qué
|
|
280
|
+
* pasó sin tener que leer JSON. El response sigue guardado al lado.
|
|
281
|
+
*/
|
|
282
|
+
function explicarFallo(args) {
|
|
283
|
+
const { nodeType, statusCode } = args;
|
|
284
|
+
const r = (args.response ?? {});
|
|
285
|
+
const nombre = args.nodeName ? `"${args.nodeName}"` : 'This node';
|
|
286
|
+
/* Un filtro que bloquea NO es una avería, y decirlo importa: su 403 se lee
|
|
287
|
+
como un problema de permisos. Es el caso que destapó la auditoría. */
|
|
288
|
+
if (nodeType === 'filter' && r.passed === false) {
|
|
289
|
+
const total = Number(r.totalItems ?? 0);
|
|
290
|
+
const pasaron = Number(r.passedItems ?? 0);
|
|
291
|
+
return total > 1
|
|
292
|
+
? `${nombre} blocked the event: ${pasaron} of ${total} items matched the rules, so nothing continued downstream. This is the filter doing its job, not a failure.`
|
|
293
|
+
: `${nombre} blocked the event: the payload did not match the rules, so nothing continued downstream. This is the filter doing its job, not a failure.`;
|
|
294
|
+
}
|
|
295
|
+
/* Un mensaje que el propio nodo escribió gana a cualquier frase nuestra:
|
|
296
|
+
lo puso quien sabía qué pasaba. */
|
|
297
|
+
const propio = (typeof r.error === 'string' && r.error) ||
|
|
298
|
+
(typeof r.message === 'string' && r.message) ||
|
|
299
|
+
null;
|
|
300
|
+
if (propio)
|
|
301
|
+
return propio;
|
|
302
|
+
if (nodeType === 'schemaValidator') {
|
|
303
|
+
return `${nombre} rejected the payload: it does not match the schema.`;
|
|
304
|
+
}
|
|
305
|
+
if (typeof statusCode === 'number' && statusCode > 0) {
|
|
306
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
307
|
+
return `${nombre} was refused by the service (${statusCode}). The credential is usually the cause: expired, revoked, or missing the scope this operation needs.`;
|
|
308
|
+
}
|
|
309
|
+
if (statusCode === 404) {
|
|
310
|
+
return `${nombre} asked for something the service could not find (404). Check the ids in the configuration.`;
|
|
311
|
+
}
|
|
312
|
+
if (statusCode === 429) {
|
|
313
|
+
return `${nombre} was rate-limited by the service (429). It ran too often for the plan or quota in use.`;
|
|
314
|
+
}
|
|
315
|
+
if (statusCode >= 500) {
|
|
316
|
+
return `${nombre} failed on the service's side (${statusCode}). Nothing is wrong with the configuration; retrying later usually works.`;
|
|
317
|
+
}
|
|
318
|
+
if (statusCode >= 400) {
|
|
319
|
+
return `${nombre} was rejected by the service (${statusCode}). The request was built from the configuration, so that is where to look.`;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return `${nombre} did not finish successfully.`;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Las claves de antes, que la PANTALLA sigue teniendo que leer.
|
|
326
|
+
*
|
|
327
|
+
* No se borran de los logs ya escritos, así que el dashboard mira primero la
|
|
328
|
+
* nueva y luego estas. Se listan aquí para que el día que se dejen de
|
|
329
|
+
* escribir haya un sitio donde mirar cuáles eran.
|
|
330
|
+
*/
|
|
331
|
+
exports.LEER_TAMBIEN_LAS_VIEJAS = {
|
|
332
|
+
payload: ['requestPayload', 'payloadPreview'],
|
|
333
|
+
response: ['responseBody'],
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* Los metadatos de un log de nodo, iguales vengan del camino que vengan.
|
|
337
|
+
*
|
|
338
|
+
* `extra` es lo que aporta cada nodo (`filterMode`, `attempt`, `targetUrl`…)
|
|
339
|
+
* y va primero, para que no pueda pisar a los tres campos que esta función
|
|
340
|
+
* garantiza.
|
|
341
|
+
*/
|
|
342
|
+
function construirMetadatosDeLog(args) {
|
|
343
|
+
const response = parsearResponse(args.responseBody);
|
|
344
|
+
/* La frase se calcula ANTES de podar, porque entra DENTRO del response.
|
|
345
|
+
Antes salía aparte, en `errorSummary`, y la pantalla la pintaba en una
|
|
346
|
+
caja roja encima del panel. Se junta con el veredicto a propósito: lo
|
|
347
|
+
que se quiere leer de un log que falló es «qué contestó el nodo», y eso
|
|
348
|
+
son las dos cosas a la vez, no una arriba y otra dentro. */
|
|
349
|
+
const fallo = args.success
|
|
350
|
+
? undefined
|
|
351
|
+
: explicarFallo({
|
|
352
|
+
nodeType: args.nodeType,
|
|
353
|
+
nodeName: args.nodeName,
|
|
354
|
+
statusCode: args.statusCode,
|
|
355
|
+
response,
|
|
356
|
+
});
|
|
357
|
+
/* La poda compara los ORIGINALES, antes de recortar: si se comparara
|
|
358
|
+
después, dos recortes distintos del mismo dato parecerían datos
|
|
359
|
+
distintos y la repetición se colaría justo en los payloads grandes. */
|
|
360
|
+
const responsePodado = podarLoRepetido(response, args.payload, fallo);
|
|
361
|
+
const payload = recortarEstructural(args.payload);
|
|
362
|
+
const responseRecortado = recortarEstructural(responsePodado);
|
|
363
|
+
const viejas = args.conClavesViejas !== false;
|
|
364
|
+
return {
|
|
365
|
+
...(args.extra ?? {}),
|
|
366
|
+
...(payload !== undefined ? { payload } : {}),
|
|
367
|
+
...(responseRecortado !== undefined ? { response: responseRecortado } : {}),
|
|
368
|
+
/* Compatibilidad con la pantalla ya desplegada. Se quitan cuando el
|
|
369
|
+
dashboard que lee `payload`/`response` esté en prod. */
|
|
370
|
+
...(viejas && responseRecortado !== undefined
|
|
371
|
+
? { responseBody: responseRecortado }
|
|
372
|
+
: {}),
|
|
373
|
+
...(viejas && payload !== undefined ? { requestPayload: payload } : {}),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node Dispatch Registry — re-exports from @hostwebhook/node-types shared package.
|
|
3
|
+
* Adds backward-compatible array form and getAllOutputs runtime helper.
|
|
4
|
+
*/
|
|
5
|
+
export type { NodeDispatchConfig } from '@hostwebhook/node-types';
|
|
6
|
+
export { NODE_DISPATCH, getAllNodeCollections, getNodeDispatchConfig, } from '@hostwebhook/node-types';
|
|
7
|
+
/** Array form for backward compat (event-pipeline uses .find()) */
|
|
8
|
+
export declare const NODE_DISPATCH_REGISTRY: {
|
|
9
|
+
service: string;
|
|
10
|
+
collection: string;
|
|
11
|
+
hasFilters: boolean;
|
|
12
|
+
outputFields: string[];
|
|
13
|
+
customDispatch: boolean;
|
|
14
|
+
pipelineDispatch: import("@hostwebhook/node-types/dist/types").PipelineDispatchKind;
|
|
15
|
+
type: string;
|
|
16
|
+
}[];
|
|
17
|
+
/**
|
|
18
|
+
* Unconditional downstream targets — the edges that fire whenever the node
|
|
19
|
+
* succeeds, with no branch decision involved.
|
|
20
|
+
*
|
|
21
|
+
* A named port (`branch:…`, `output:…`, `category:…`, `rule:…`, `else`,
|
|
22
|
+
* `loop`, `done`, `rejected`) is NOT unconditional: its owner decides
|
|
23
|
+
* whether it fires, via the handler's own `getOutputNodes`. Including one
|
|
24
|
+
* here would fan a payload down every branch at once. That is why the
|
|
25
|
+
* `main` filter below is load-bearing and not a tidy-up — Phase 1 moved
|
|
26
|
+
* every edge into `outputNodes`, so without it this function would return
|
|
27
|
+
* branches it never saw before.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getAllOutputs(node: any): Array<{
|
|
30
|
+
nodeType: string;
|
|
31
|
+
nodeId: string;
|
|
32
|
+
}>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Node Dispatch Registry — re-exports from @hostwebhook/node-types shared package.
|
|
4
|
+
* Adds backward-compatible array form and getAllOutputs runtime helper.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.NODE_DISPATCH_REGISTRY = exports.getNodeDispatchConfig = exports.getAllNodeCollections = exports.NODE_DISPATCH = void 0;
|
|
8
|
+
exports.getAllOutputs = getAllOutputs;
|
|
9
|
+
const node_types_1 = require("@hostwebhook/node-types");
|
|
10
|
+
var node_types_2 = require("@hostwebhook/node-types");
|
|
11
|
+
Object.defineProperty(exports, "NODE_DISPATCH", { enumerable: true, get: function () { return node_types_2.NODE_DISPATCH; } });
|
|
12
|
+
Object.defineProperty(exports, "getAllNodeCollections", { enumerable: true, get: function () { return node_types_2.getAllNodeCollections; } });
|
|
13
|
+
Object.defineProperty(exports, "getNodeDispatchConfig", { enumerable: true, get: function () { return node_types_2.getNodeDispatchConfig; } });
|
|
14
|
+
/** Array form for backward compat (event-pipeline uses .find()) */
|
|
15
|
+
exports.NODE_DISPATCH_REGISTRY = Object.entries(node_types_1.NODE_DISPATCH).map(([type, config]) => ({ type, ...config }));
|
|
16
|
+
/**
|
|
17
|
+
* Unconditional downstream targets — the edges that fire whenever the node
|
|
18
|
+
* succeeds, with no branch decision involved.
|
|
19
|
+
*
|
|
20
|
+
* A named port (`branch:…`, `output:…`, `category:…`, `rule:…`, `else`,
|
|
21
|
+
* `loop`, `done`, `rejected`) is NOT unconditional: its owner decides
|
|
22
|
+
* whether it fires, via the handler's own `getOutputNodes`. Including one
|
|
23
|
+
* here would fan a payload down every branch at once. That is why the
|
|
24
|
+
* `main` filter below is load-bearing and not a tidy-up — Phase 1 moved
|
|
25
|
+
* every edge into `outputNodes`, so without it this function would return
|
|
26
|
+
* branches it never saw before.
|
|
27
|
+
*/
|
|
28
|
+
function getAllOutputs(node) {
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const result = [];
|
|
31
|
+
const add = (nodeType, nodeId) => {
|
|
32
|
+
const id = nodeId?.toString?.() ?? String(nodeId);
|
|
33
|
+
const key = `${nodeType}:${id}`;
|
|
34
|
+
if (!seen.has(key)) {
|
|
35
|
+
seen.add(key);
|
|
36
|
+
result.push({ nodeType, nodeId: id });
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
for (const n of node.outputNodes ?? []) {
|
|
40
|
+
if ((n.port ?? 'main') !== 'main')
|
|
41
|
+
continue;
|
|
42
|
+
add(n.nodeType, n.nodeId);
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|