@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,20 @@
1
+ import { type CodeRunnerContext, type CodeRunnerResult } from './pre-request-pool';
2
+ /**
3
+ * Un fallo del CÓDIGO del inquilino, no del transporte.
4
+ *
5
+ * Se distingue a propósito: que el código del usuario lance no es motivo para
6
+ * caer al hijo — allí lanzaría igual, y reintentarlo sólo duplicaría efectos.
7
+ * Sólo el transporte justifica el respaldo.
8
+ */
9
+ export declare class CodeNodeError extends Error {
10
+ readonly logs: string[];
11
+ constructor(message: string, logs: string[], name?: string);
12
+ }
13
+ /**
14
+ * Ejecutar el código, por el camino más seguro que esté disponible.
15
+ *
16
+ * Devuelve la misma forma corra donde corra — el llamante no se entera de
17
+ * cuál fue, que es la condición para que el respaldo sea un respaldo y no
18
+ * otro comportamiento.
19
+ */
20
+ export declare function runTenantCode(code: string, ctx: CodeRunnerContext, timeoutMs: number): Promise<CodeRunnerResult>;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CodeNodeError = void 0;
4
+ exports.runTenantCode = runTenantCode;
5
+ const common_1 = require("@nestjs/common");
6
+ const pre_request_pool_1 = require("./pre-request-pool");
7
+ /**
8
+ * Dónde corre el código de un nodo de código.
9
+ *
10
+ * ## Por qué hay dos sitios
11
+ *
12
+ * Sacarlo del proceso de la API quitó los secretos y el disco: el hijo arranca
13
+ * con entorno vacío y, donde el runtime lo soporta, con ficheros denegados.
14
+ * Pero el modelo de permisos de Node NO cubre la red — medido: un escape del
15
+ * vm dentro del hijo encuentra `require('http')` disponible. Y ese hijo corre
16
+ * DENTRO de la red privada.
17
+ *
18
+ * El worker de Cloudflare no arregla el sandbox, arregla la POSICIÓN: allí no
19
+ * hay red privada que alcanzar. Por eso es el camino preferido.
20
+ *
21
+ * ## El respaldo, y por qué incomoda
22
+ *
23
+ * Si el worker no responde se cae al hijo. Eso protege la disponibilidad y
24
+ * cede exactamente lo que el worker vino a comprar, así que no puede ser
25
+ * silencioso ni permanente:
26
+ *
27
+ * - cada caída se registra a nivel ERROR, con el motivo. Un fallo sostenido
28
+ * tiene que verse, no acumularse;
29
+ * - `CODE_RUNNER_STRICT=1` lo desactiva. Con eso puesto, si el worker falla
30
+ * el nodo falla — que es lo que querrás en producción el día que confíes
31
+ * en el worker, porque así la seguridad deja de depender de que un
32
+ * servicio externo esté sano.
33
+ *
34
+ * Sin worker configurado no hay aviso ninguno: es el caso de desarrollo, y
35
+ * ahí el hijo es el camino normal, no una degradación.
36
+ */
37
+ const logger = new common_1.Logger('CodeRunner');
38
+ /** Margen sobre el timeout del propio código, para distinguir "tu bucle no
39
+ * terminó" de "el worker no contestó". */
40
+ const MARGEN_DE_RED_MS = 3_000;
41
+ function urlDelWorker() {
42
+ return (process.env.CLOUDFLARE_CODE_RUNNER_URL ?? '').trim() || null;
43
+ }
44
+ function secretoDelWorker() {
45
+ return (process.env.CLOUDFLARE_CODE_RUNNER_SECRET ?? '').trim() || null;
46
+ }
47
+ /** Con esto puesto, un worker caído hace fallar el nodo en vez de caer al hijo. */
48
+ function esEstricto() {
49
+ const raw = (process.env.CODE_RUNNER_STRICT ?? '').trim().toLowerCase();
50
+ return raw === '1' || raw === 'true';
51
+ }
52
+ /**
53
+ * Un fallo del CÓDIGO del inquilino, no del transporte.
54
+ *
55
+ * Se distingue a propósito: que el código del usuario lance no es motivo para
56
+ * caer al hijo — allí lanzaría igual, y reintentarlo sólo duplicaría efectos.
57
+ * Sólo el transporte justifica el respaldo.
58
+ */
59
+ class CodeNodeError extends Error {
60
+ logs;
61
+ constructor(message, logs, name = 'Error') {
62
+ super(message);
63
+ this.logs = logs;
64
+ this.name = name;
65
+ }
66
+ }
67
+ exports.CodeNodeError = CodeNodeError;
68
+ async function correrEnWorker(url, secreto, code, ctx, timeoutMs) {
69
+ const abort = new AbortController();
70
+ const corte = setTimeout(() => abort.abort(), timeoutMs + MARGEN_DE_RED_MS);
71
+ let res;
72
+ try {
73
+ res = await fetch(url, {
74
+ method: 'POST',
75
+ headers: {
76
+ 'content-type': 'application/json',
77
+ authorization: `Bearer ${secreto}`,
78
+ },
79
+ body: JSON.stringify({
80
+ code,
81
+ payload: ctx.payload,
82
+ workspacePayloads: ctx.workspacePayloads,
83
+ timeoutMs,
84
+ }),
85
+ signal: abort.signal,
86
+ });
87
+ }
88
+ finally {
89
+ clearTimeout(corte);
90
+ }
91
+ if (!res.ok && res.status !== 200) {
92
+ // 401, 5xx, un 1102 de la plataforma… todo esto es transporte.
93
+ throw new Error(`code runner worker responded ${res.status}`);
94
+ }
95
+ const cuerpo = (await res.json());
96
+ const logs = Array.isArray(cuerpo.logs) ? cuerpo.logs.map(String) : [];
97
+ if (cuerpo.ok === true) {
98
+ return { output: cuerpo.output ?? null, logs };
99
+ }
100
+ // El worker contestó y dice que el código falló. Eso es un resultado, no una
101
+ // avería: se propaga tal cual y no se reintenta en el hijo.
102
+ throw new CodeNodeError(String(cuerpo.message ?? 'Code node failed'), logs, String(cuerpo.name ?? 'Error'));
103
+ }
104
+ /**
105
+ * Ejecutar el código, por el camino más seguro que esté disponible.
106
+ *
107
+ * Devuelve la misma forma corra donde corra — el llamante no se entera de
108
+ * cuál fue, que es la condición para que el respaldo sea un respaldo y no
109
+ * otro comportamiento.
110
+ */
111
+ async function runTenantCode(code, ctx, timeoutMs) {
112
+ const url = urlDelWorker();
113
+ const secreto = secretoDelWorker();
114
+ if (!url || !secreto) {
115
+ // Desarrollo, o antes de desplegar el worker. El hijo es el camino normal
116
+ // aquí, no una degradación, así que no se avisa de nada.
117
+ return (0, pre_request_pool_1.runCodeNodeInChild)(code, ctx, timeoutMs);
118
+ }
119
+ try {
120
+ return await correrEnWorker(url, secreto, code, ctx, timeoutMs);
121
+ }
122
+ catch (err) {
123
+ // El código del inquilino falló y el worker lo dijo. No es una avería.
124
+ if (err instanceof CodeNodeError)
125
+ throw err;
126
+ const motivo = err instanceof Error ? err.message : String(err);
127
+ if (esEstricto()) {
128
+ logger.error(`[code-runner] el worker no respondió y CODE_RUNNER_STRICT está puesto — el nodo falla: ${motivo}`);
129
+ throw new Error(`Code runner unavailable: ${motivo}`);
130
+ }
131
+ // A nivel ERROR a propósito: esto devuelve la ejecución a la red privada,
132
+ // que es justo lo que el worker vino a evitar. Un fallo sostenido tiene
133
+ // que doler en el panel, no acumularse en silencio.
134
+ logger.error(`[code-runner] el worker no respondió (${motivo}) — se ejecuta en el proceso hijo local. ` +
135
+ 'Eso devuelve el código de inquilino a la red privada; si se repite, míralo.');
136
+ return (0, pre_request_pool_1.runCodeNodeInChild)(code, ctx, timeoutMs);
137
+ }
138
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Los tipos que el SDK necesita del mundo de fuera, declarados AQUÍ.
3
+ *
4
+ * ## Por qué no se importan
5
+ *
6
+ * Porque el paquete no puede alcanzar `src/` de la api. Los siete vivían en
7
+ * entidades de Mongoose y servicios de Nest —`webhook.entity`,
8
+ * `telemetry.service`, `user.entity`…— y llegaban por `import type`, que se
9
+ * borra al compilar. Dentro del monolito eso funcionaba; en un paquete, no.
10
+ *
11
+ * ## Se declara la superficie USADA, no la entidad entera
12
+ *
13
+ * Está medido, no adivinado: de `TelemetryService` sólo se llaman dos métodos;
14
+ * de `UserDocument` sólo se leen cuatro campos. Copiar la entidad completa
15
+ * traería `@Prop`, el esquema, y con ellos media aplicación.
16
+ *
17
+ * ⚠️ **La dirección de la dependencia se invierte.** A partir de aquí el
18
+ * contrato lo define el SDK y la api lo cumple: sus entidades reexportan
19
+ * estos tipos en vez de definirlos. Si alguien añade un campo en la entidad y
20
+ * no aquí, el SDK sencillamente no lo ve — que es lo correcto: el SDK no
21
+ * conoce las entidades de nadie.
22
+ */
23
+ /**
24
+ * Los operadores que el SDK sabe EVALUAR, sacados de `filter-utils`.
25
+ *
26
+ * ⚠️ No coinciden con los `enum` de las entidades de la api, y esa es la
27
+ * lista buena: aquí vive el evaluador. Los enum de allá van de 7 a 32 segun
28
+ * la entidad —`webhook` admite 7 de estos 30— asi que hay filtros
29
+ * que el motor sabe resolver y la base de datos rechaza al guardar. Es un
30
+ * fallo de la api, anterior a este paquete, y se arregla aparte.
31
+ */
32
+ export type FilterOperator = 'array_contains' | 'array_empty' | 'array_length_eq' | 'array_length_gt' | 'array_length_lt' | 'array_not_contains' | 'array_not_empty' | 'contains' | 'date_after' | 'date_before' | 'date_eq' | 'ends_with' | 'eq' | 'exists' | 'gt' | 'gte' | 'has_key' | 'is_empty' | 'is_false' | 'is_not_empty' | 'is_true' | 'lt' | 'lte' | 'matches_regex' | 'neq' | 'not_contains' | 'not_ends_with' | 'not_exists' | 'not_starts_with' | 'starts_with';
33
+ export interface PayloadFilter {
34
+ field: string;
35
+ operator: FilterOperator;
36
+ value?: string;
37
+ enabled?: boolean;
38
+ }
39
+ export interface SchemaFieldConstraints {
40
+ minLength?: number;
41
+ maxLength?: number;
42
+ pattern?: string;
43
+ format?: 'email' | 'url' | 'uuid' | 'iso-date';
44
+ min?: number;
45
+ max?: number;
46
+ enum?: string[];
47
+ minItems?: number;
48
+ maxItems?: number;
49
+ }
50
+ export interface SchemaField {
51
+ path: string;
52
+ type: 'string' | 'number' | 'boolean' | 'array' | 'object' | 'any';
53
+ required: boolean;
54
+ constraints?: SchemaFieldConstraints;
55
+ }
56
+ /**
57
+ * `skipped` es un estado de primera y no un `failed` descafeinado: un paso que
58
+ * no llegó a correr porque el anterior falló no es lo mismo que uno que sí
59
+ * corrió y salió mal, y la pantalla necesita distinguirlos para enseñar dónde
60
+ * se cortó el flujo.
61
+ */
62
+ export type RunStepStatus = 'success' | 'failed' | 'skipped';
63
+ export interface PasoEjecutado {
64
+ organizationId: string;
65
+ correlationId: string;
66
+ nodeId: string;
67
+ nodeName?: string;
68
+ nodeType?: string;
69
+ status: RunStepStatus;
70
+ startedAt: Date;
71
+ durationMs: number;
72
+ error?: string;
73
+ /** El origen de la corrida, para poder abrirla en la primera fila. */
74
+ sourceId?: string;
75
+ sourceName?: string;
76
+ workspaceId?: string;
77
+ entrada?: unknown;
78
+ salida?: unknown;
79
+ metadata?: Record<string, unknown>;
80
+ /** Cómo se disparó la corrida. Ausente = producción. */
81
+ modo?: 'pipeline';
82
+ }
83
+ /** Quien anota los pasos. El SDK sólo necesita saber que existe el método. */
84
+ export interface RegistradorDeHistorial {
85
+ registrarPaso(paso: PasoEjecutado): unknown;
86
+ }
87
+ /**
88
+ * De dónde viene una línea de registro. Es `string` a propósito y no una
89
+ * unión cerrada: cada tipo de nodo aporta el suyo, y una unión obligaría a
90
+ * publicar el paquete cada vez que nace un nodo.
91
+ */
92
+ export type LogSource = string;
93
+ /**
94
+ * Lo único que el ciclo de vida le pide a la telemetría.
95
+ *
96
+ * Dos métodos, medidos sobre `node-lifecycle`: `emit` para lo que puede
97
+ * perderse y `emitAndWait` para lo que no. El servicio real de la api tiene
98
+ * muchos más; ninguno se usa desde aquí.
99
+ */
100
+ export interface EmisorDeTelemetria {
101
+ emit(...args: unknown[]): unknown;
102
+ emitAndWait(...args: unknown[]): Promise<unknown>;
103
+ }
104
+ /**
105
+ * Lo que `request-context` lee de `req.user`, y nada más.
106
+ *
107
+ * ⚠️ `__isApiKey` y `__apiKeyScopes` llevan doble guion bajo porque no son
108
+ * campos del documento: los pone la estrategia de autenticación al vuelo para
109
+ * distinguir una sesión del panel —que actúa por la cuenta entera— de una
110
+ * llave, que sólo tiene lo que se le acuñó.
111
+ */
112
+ export interface IdentidadDePeticion {
113
+ _id: {
114
+ toString(): string;
115
+ };
116
+ organizationId?: {
117
+ toString(): string;
118
+ };
119
+ __isApiKey?: boolean;
120
+ __apiKeyScopes?: string[];
121
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ /**
3
+ * Los tipos que el SDK necesita del mundo de fuera, declarados AQUÍ.
4
+ *
5
+ * ## Por qué no se importan
6
+ *
7
+ * Porque el paquete no puede alcanzar `src/` de la api. Los siete vivían en
8
+ * entidades de Mongoose y servicios de Nest —`webhook.entity`,
9
+ * `telemetry.service`, `user.entity`…— y llegaban por `import type`, que se
10
+ * borra al compilar. Dentro del monolito eso funcionaba; en un paquete, no.
11
+ *
12
+ * ## Se declara la superficie USADA, no la entidad entera
13
+ *
14
+ * Está medido, no adivinado: de `TelemetryService` sólo se llaman dos métodos;
15
+ * de `UserDocument` sólo se leen cuatro campos. Copiar la entidad completa
16
+ * traería `@Prop`, el esquema, y con ellos media aplicación.
17
+ *
18
+ * ⚠️ **La dirección de la dependencia se invierte.** A partir de aquí el
19
+ * contrato lo define el SDK y la api lo cumple: sus entidades reexportan
20
+ * estos tipos en vez de definirlos. Si alguien añade un campo en la entidad y
21
+ * no aquí, el SDK sencillamente no lo ve — que es lo correcto: el SDK no
22
+ * conoce las entidades de nadie.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,19 @@
1
+ /** Common aliases LLMs and clients may send → canonical nodeType */
2
+ export declare const NODE_TYPE_ALIASES: Record<string, string>;
3
+ export declare function normalizeNodeType(type: string): string;
4
+ export declare class OutputNodeDto {
5
+ nodeType: string;
6
+ nodeId: string;
7
+ /**
8
+ * Which port the edge leaves through — see
9
+ * common/output-node-ref.schema.ts. Optional on the wire: absent means
10
+ * `main`, which is what every edge written before Phase 1 meant.
11
+ *
12
+ * Not validated against a fixed list on purpose. Container-owned ports
13
+ * are `<kind>:<container id>`, so the valid set is per-document and a
14
+ * whitelist here would either be wrong or duplicate the entity.
15
+ */
16
+ port?: string;
17
+ sourceHandle?: string;
18
+ targetHandle?: string;
19
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.OutputNodeDto = exports.NODE_TYPE_ALIASES = void 0;
13
+ exports.normalizeNodeType = normalizeNodeType;
14
+ const class_validator_1 = require("class-validator");
15
+ const class_transformer_1 = require("class-transformer");
16
+ const node_types_1 = require("@hostwebhook/node-types");
17
+ /** Common aliases LLMs and clients may send → canonical nodeType */
18
+ exports.NODE_TYPE_ALIASES = {
19
+ mergeNode: 'merge',
20
+ approvalNode: 'approval',
21
+ filterNode: 'filter',
22
+ transformNode: 'transform',
23
+ delayNode: 'delay',
24
+ conditionalNode: 'conditional',
25
+ rateLimiterNode: 'rateLimiter',
26
+ aggregatorNode: 'aggregator',
27
+ cacheNode: 'cache',
28
+ schemaValidatorNode: 'schemaValidator',
29
+ email: 'emailAction',
30
+ http: 'httpAction',
31
+ mongo: 'mongoAction',
32
+ notification: 'notificationAction',
33
+ sheets: 'sheetsAction',
34
+ 'scheduled-workflow': 'scheduledWorkflow',
35
+ scheduledwebhook: 'scheduledWorkflow',
36
+ aiNode: 'ai',
37
+ loopNode: 'loop',
38
+ splitNode: 'split',
39
+ markdownNode: 'markdown',
40
+ fileTransformNode: 'fileTransform',
41
+ calendar: 'calendarAction',
42
+ docs: 'docsAction',
43
+ firecrawl: 'firecrawlAction',
44
+ // Legacy alias — pre-2026-04 the source node was `voiceCall` (one
45
+ // per tool). The fold-voice-call-triggers migration replaced it with
46
+ // `voiceAgent` (one per agent, N output handles by sourceHandle=toolName).
47
+ // Old payloads / saved flows still send the literal "voiceCall" until
48
+ // the dashboard rewrites them on next save — normalise here so the
49
+ // pipeline doesn't reject inflight data.
50
+ voiceCall: 'voiceAgent',
51
+ };
52
+ function normalizeNodeType(type) {
53
+ return exports.NODE_TYPE_ALIASES[type] ?? type;
54
+ }
55
+ class OutputNodeDto {
56
+ nodeType;
57
+ nodeId;
58
+ /**
59
+ * Which port the edge leaves through — see
60
+ * common/output-node-ref.schema.ts. Optional on the wire: absent means
61
+ * `main`, which is what every edge written before Phase 1 meant.
62
+ *
63
+ * Not validated against a fixed list on purpose. Container-owned ports
64
+ * are `<kind>:<container id>`, so the valid set is per-document and a
65
+ * whitelist here would either be wrong or duplicate the entity.
66
+ */
67
+ port;
68
+ sourceHandle;
69
+ targetHandle;
70
+ }
71
+ exports.OutputNodeDto = OutputNodeDto;
72
+ __decorate([
73
+ (0, class_transformer_1.Transform)(({ value }) => exports.NODE_TYPE_ALIASES[value] ?? value),
74
+ (0, class_validator_1.IsIn)(node_types_1.ALL_NODE_TYPES),
75
+ __metadata("design:type", String)
76
+ ], OutputNodeDto.prototype, "nodeType", void 0);
77
+ __decorate([
78
+ (0, class_validator_1.IsMongoId)(),
79
+ __metadata("design:type", String)
80
+ ], OutputNodeDto.prototype, "nodeId", void 0);
81
+ __decorate([
82
+ (0, class_validator_1.IsOptional)(),
83
+ (0, class_validator_1.IsString)(),
84
+ (0, class_validator_1.MaxLength)(80),
85
+ __metadata("design:type", String)
86
+ ], OutputNodeDto.prototype, "port", void 0);
87
+ __decorate([
88
+ (0, class_validator_1.IsOptional)(),
89
+ (0, class_validator_1.IsString)(),
90
+ __metadata("design:type", String)
91
+ ], OutputNodeDto.prototype, "sourceHandle", void 0);
92
+ __decorate([
93
+ (0, class_validator_1.IsOptional)(),
94
+ (0, class_validator_1.IsString)(),
95
+ __metadata("design:type", String)
96
+ ], OutputNodeDto.prototype, "targetHandle", void 0);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Backstop for the standardised `_meta` contract documented in
3
+ * `@hostwebhook/node-types` (`PayloadMeta`):
4
+ *
5
+ * { iterable: boolean; count: number; iterateField?: string }
6
+ *
7
+ * Most pipeline / transform / fetch nodes set `_meta` explicitly. Action
8
+ * nodes that proxy a third-party API (slack/discord/telegram/whatsapp/
9
+ * etc.) emit the raw API response as their output, and the contract
10
+ * was easy to forget when adding a new operation. Calling `ensureMeta`
11
+ * right before JSON-stringifying the result guarantees every downstream
12
+ * consumer (Limit / Loop / Filter / Aggregator / `$()` cross-node refs)
13
+ * sees a contract-compliant payload.
14
+ *
15
+ * Behavior:
16
+ * - If `output` already has a `_meta` field, returned unchanged.
17
+ * - If not, wraps with the safest default: a single, non-iterable
18
+ * object. Operations that DO return arrays should set
19
+ * `_meta: { iterable: true, iterateField, count }` themselves —
20
+ * this helper is the safety net, not a heuristic guesser.
21
+ */
22
+ export declare function ensureMeta<T extends Record<string, unknown> | null | undefined>(output: T): Record<string, unknown>;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureMeta = ensureMeta;
4
+ /**
5
+ * Backstop for the standardised `_meta` contract documented in
6
+ * `@hostwebhook/node-types` (`PayloadMeta`):
7
+ *
8
+ * { iterable: boolean; count: number; iterateField?: string }
9
+ *
10
+ * Most pipeline / transform / fetch nodes set `_meta` explicitly. Action
11
+ * nodes that proxy a third-party API (slack/discord/telegram/whatsapp/
12
+ * etc.) emit the raw API response as their output, and the contract
13
+ * was easy to forget when adding a new operation. Calling `ensureMeta`
14
+ * right before JSON-stringifying the result guarantees every downstream
15
+ * consumer (Limit / Loop / Filter / Aggregator / `$()` cross-node refs)
16
+ * sees a contract-compliant payload.
17
+ *
18
+ * Behavior:
19
+ * - If `output` already has a `_meta` field, returned unchanged.
20
+ * - If not, wraps with the safest default: a single, non-iterable
21
+ * object. Operations that DO return arrays should set
22
+ * `_meta: { iterable: true, iterateField, count }` themselves —
23
+ * this helper is the safety net, not a heuristic guesser.
24
+ */
25
+ function ensureMeta(output) {
26
+ if (output &&
27
+ typeof output === 'object' &&
28
+ output._meta) {
29
+ return output;
30
+ }
31
+ return {
32
+ _meta: { iterable: false, count: 1 },
33
+ ...(output ?? {}),
34
+ };
35
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Infrastructure-level iteration wrapper.
3
+ *
4
+ * Nodes only implement single-item execution. This helper detects
5
+ * _meta.iterable and automatically iterates, aggregating results.
6
+ * Nodes never see _meta — they receive clean individual payloads.
7
+ */
8
+ type SingleExecutor = (payload: Record<string, unknown>) => Promise<{
9
+ statusCode: number;
10
+ responseBody: string;
11
+ latencyMs: number;
12
+ }>;
13
+ export declare function executeWithIteration(payload: Record<string, unknown>, executeSingle: SingleExecutor): Promise<{
14
+ statusCode: number;
15
+ responseBody: string;
16
+ latencyMs: number;
17
+ }>;
18
+ export {};
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ /**
3
+ * Infrastructure-level iteration wrapper.
4
+ *
5
+ * Nodes only implement single-item execution. This helper detects
6
+ * _meta.iterable and automatically iterates, aggregating results.
7
+ * Nodes never see _meta — they receive clean individual payloads.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.executeWithIteration = executeWithIteration;
11
+ async function executeWithIteration(payload, executeSingle) {
12
+ const meta = payload._meta;
13
+ if (meta?.iterable && meta.iterateField) {
14
+ const items = payload[meta.iterateField] ?? [];
15
+ const results = [];
16
+ let totalLatency = 0;
17
+ let lastStatus = 200;
18
+ for (const item of items) {
19
+ const clean = typeof item === 'object' && item !== null
20
+ ? { ...item }
21
+ : { value: item };
22
+ delete clean._meta;
23
+ const res = await executeSingle(clean);
24
+ totalLatency += res.latencyMs;
25
+ lastStatus = res.statusCode;
26
+ try {
27
+ const parsed = JSON.parse(res.responseBody);
28
+ // If executor returns an array, spread its items instead of nesting [[...]]
29
+ if (Array.isArray(parsed)) {
30
+ for (const entry of parsed)
31
+ results.push(entry);
32
+ }
33
+ else {
34
+ results.push(parsed);
35
+ }
36
+ }
37
+ catch {
38
+ results.push(res.responseBody);
39
+ }
40
+ }
41
+ // Single result from single iteration → unwrap as plain object
42
+ if (results.length === 1 &&
43
+ typeof results[0] === 'object' &&
44
+ results[0] !== null) {
45
+ const single = { _meta: { iterable: false, count: 1 }, ...results[0] };
46
+ return {
47
+ statusCode: lastStatus,
48
+ responseBody: JSON.stringify(single),
49
+ latencyMs: totalLatency,
50
+ };
51
+ }
52
+ const aggregated = {
53
+ _meta: { iterable: true, iterateField: 'results', count: results.length },
54
+ results,
55
+ };
56
+ return {
57
+ statusCode: lastStatus,
58
+ responseBody: JSON.stringify(aggregated),
59
+ latencyMs: totalLatency,
60
+ };
61
+ }
62
+ // Single item — strip _meta and execute
63
+ const clean = { ...payload };
64
+ delete clean._meta;
65
+ return executeSingle(clean);
66
+ }
@@ -0,0 +1,22 @@
1
+ import type { PayloadFilter } from './contratos';
2
+ export declare function getNestedValue(obj: unknown, path: string): unknown;
3
+ export interface FilterResolveOpts {
4
+ /**
5
+ * @deprecated Sin efecto: el motor de plantillas retiró el evaluador de JS.
6
+ * Se mantiene el campo para no obligar a tocar todos los llamantes a la vez.
7
+ */
8
+ allowJs?: boolean;
9
+ workspacePayloads?: Record<string, Record<string, unknown>>;
10
+ }
11
+ export declare function evaluateFilters(filters: PayloadFilter[], payload: unknown, mode?: 'and' | 'or', opts?: FilterResolveOpts): boolean;
12
+ /**
13
+ * Evaluate filters against a payload that may be iterable.
14
+ * If iterable: filters each item, returns a new iterable payload with only passing items.
15
+ * If not iterable: returns { passed, payload } with the original payload.
16
+ */
17
+ export declare function evaluateFiltersIterable(filters: PayloadFilter[], payload: Record<string, unknown>, mode?: 'and' | 'or', outputAsSingle?: boolean, opts?: FilterResolveOpts): {
18
+ passed: boolean;
19
+ resultPayload: Record<string, unknown>;
20
+ totalItems: number;
21
+ passedItems: number;
22
+ };