@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,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
+ }