@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,18 @@
1
+ import { Types } from 'mongoose';
2
+ /**
3
+ * Normalizes an array of input/output node DTOs into Mongoose-ready documents.
4
+ * Extracts the common pattern used in every node service's create() and update().
5
+ */
6
+ export declare function normalizeNodes(nodes?: Array<{
7
+ nodeType: string;
8
+ nodeId: string;
9
+ port?: string;
10
+ sourceHandle?: string;
11
+ targetHandle?: string;
12
+ }>): Array<{
13
+ nodeType: string;
14
+ nodeId: Types.ObjectId;
15
+ port: string;
16
+ sourceHandle?: string;
17
+ targetHandle?: string;
18
+ }>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeNodes = normalizeNodes;
4
+ const mongoose_1 = require("mongoose");
5
+ const output_node_dto_1 = require("./dto/output-node.dto");
6
+ const output_node_ref_schema_1 = require("./output-node-ref.schema");
7
+ /**
8
+ * Normalizes an array of input/output node DTOs into Mongoose-ready documents.
9
+ * Extracts the common pattern used in every node service's create() and update().
10
+ */
11
+ function normalizeNodes(nodes) {
12
+ return (nodes ?? []).map((n) => ({
13
+ nodeType: (0, output_node_dto_1.normalizeNodeType)(n.nodeType),
14
+ nodeId: new mongoose_1.Types.ObjectId(n.nodeId),
15
+ // Rebuilt field by field, so anything not listed here is silently
16
+ // dropped on save — which is exactly how the port went missing the
17
+ // first time. Absent means the main port.
18
+ port: n.port || output_node_ref_schema_1.MAIN_PORT,
19
+ ...(n.sourceHandle && { sourceHandle: n.sourceHandle }),
20
+ ...(n.targetHandle && { targetHandle: n.targetHandle }),
21
+ }));
22
+ }
@@ -0,0 +1,82 @@
1
+ import { Schema as MongooseSchema, Types } from 'mongoose';
2
+ /**
3
+ * The one shape of "this node connects to that node".
4
+ *
5
+ * Phase 1 of the standardization plan. Before this, the same literal was
6
+ * pasted 58 times across 30 entities, and a node that needed a second
7
+ * outbound port invented a second field name for it — elseOutputNodes,
8
+ * loopOutputNodes, doneOutputNodes, rejectionOutputNodes, plus containers
9
+ * that nested their own `outputNodes` (branches[], outputs[],
10
+ * categories[]) and a router that stored a bare string. Nine names for one
11
+ * idea, and every consumer had to remember all of them; cascade-cleanup
12
+ * forgot one and accumulated orphan references for months.
13
+ *
14
+ * Now there is one array per node and the port lives on the edge:
15
+ *
16
+ * port: "main" the default, and what an unported edge means
17
+ * port: "else" conditional fallback
18
+ * port: "loop" | "done" loop
19
+ * port: "rejected" approval
20
+ * port: "branch:<id>" conditional, one per branches[].id
21
+ * port: "output:<id>" split, one per outputs[].id
22
+ * port: "category:<id>" ai classifier, one per categories[].id
23
+ * port: "rule:<id>" router, one per rules[].id
24
+ *
25
+ * The containers keep the condition and the label — that part really is
26
+ * per-node — but they no longer keep the destination.
27
+ *
28
+ * `sourceHandle` / `targetHandle` stay and are NOT the port: their values
29
+ * are canvas geometry ("ro-<id>-right-out" means "leaves from the right
30
+ * edge"), not semantics. They exist to draw the line.
31
+ *
32
+ * `port` is required with a default so an older client that omits it
33
+ * still saves, and lands on the main port — which is what it meant.
34
+ */
35
+ export declare const OUTPUT_NODE_REF_SCHEMA: {
36
+ readonly nodeType: {
37
+ readonly type: StringConstructor;
38
+ readonly required: true;
39
+ };
40
+ readonly nodeId: {
41
+ readonly type: typeof MongooseSchema.Types.ObjectId;
42
+ readonly required: true;
43
+ };
44
+ readonly port: {
45
+ readonly type: StringConstructor;
46
+ readonly required: true;
47
+ readonly default: "main";
48
+ };
49
+ readonly sourceHandle: StringConstructor;
50
+ readonly targetHandle: StringConstructor;
51
+ readonly _id: false;
52
+ };
53
+ /** TS counterpart of the schema above. */
54
+ export interface OutputNodeRef {
55
+ nodeType: string;
56
+ nodeId: Types.ObjectId;
57
+ port: string;
58
+ sourceHandle?: string;
59
+ targetHandle?: string;
60
+ }
61
+ export declare const MAIN_PORT = "main";
62
+ /** Ports whose name is fixed, as opposed to the `<kind>:<id>` ones. */
63
+ export declare const FIXED_PORTS: readonly ["main", "else", "loop", "done", "rejected"];
64
+ /** Build the port name for a container-owned edge. */
65
+ export declare const branchPort: (id: string) => string;
66
+ export declare const outputPort: (id: string) => string;
67
+ export declare const categoryPort: (id: string) => string;
68
+ export declare const rulePort: (id: string) => string;
69
+ /** Read the container id back out of a port name, or null if the port is
70
+ * a fixed one. */
71
+ export declare function portContainerId(port: string, kind: 'branch' | 'output' | 'category' | 'rule'): string | null;
72
+ /**
73
+ * The edges leaving `port`, in the shape the dispatcher wants.
74
+ *
75
+ * A ref written before Phase 1 has no `port` and means `main` — the same
76
+ * rule the schema default encodes, applied on read so a document that has
77
+ * not been touched since still routes correctly.
78
+ */
79
+ export declare function refsForPort(entity: Record<string, any> | null | undefined, port: string): Array<{
80
+ nodeType: string;
81
+ nodeId: string;
82
+ }>;
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rulePort = exports.categoryPort = exports.outputPort = exports.branchPort = exports.FIXED_PORTS = exports.MAIN_PORT = exports.OUTPUT_NODE_REF_SCHEMA = void 0;
4
+ exports.portContainerId = portContainerId;
5
+ exports.refsForPort = refsForPort;
6
+ const mongoose_1 = require("mongoose");
7
+ /**
8
+ * The one shape of "this node connects to that node".
9
+ *
10
+ * Phase 1 of the standardization plan. Before this, the same literal was
11
+ * pasted 58 times across 30 entities, and a node that needed a second
12
+ * outbound port invented a second field name for it — elseOutputNodes,
13
+ * loopOutputNodes, doneOutputNodes, rejectionOutputNodes, plus containers
14
+ * that nested their own `outputNodes` (branches[], outputs[],
15
+ * categories[]) and a router that stored a bare string. Nine names for one
16
+ * idea, and every consumer had to remember all of them; cascade-cleanup
17
+ * forgot one and accumulated orphan references for months.
18
+ *
19
+ * Now there is one array per node and the port lives on the edge:
20
+ *
21
+ * port: "main" the default, and what an unported edge means
22
+ * port: "else" conditional fallback
23
+ * port: "loop" | "done" loop
24
+ * port: "rejected" approval
25
+ * port: "branch:<id>" conditional, one per branches[].id
26
+ * port: "output:<id>" split, one per outputs[].id
27
+ * port: "category:<id>" ai classifier, one per categories[].id
28
+ * port: "rule:<id>" router, one per rules[].id
29
+ *
30
+ * The containers keep the condition and the label — that part really is
31
+ * per-node — but they no longer keep the destination.
32
+ *
33
+ * `sourceHandle` / `targetHandle` stay and are NOT the port: their values
34
+ * are canvas geometry ("ro-<id>-right-out" means "leaves from the right
35
+ * edge"), not semantics. They exist to draw the line.
36
+ *
37
+ * `port` is required with a default so an older client that omits it
38
+ * still saves, and lands on the main port — which is what it meant.
39
+ */
40
+ exports.OUTPUT_NODE_REF_SCHEMA = {
41
+ nodeType: { type: String, required: true },
42
+ nodeId: { type: mongoose_1.Schema.Types.ObjectId, required: true },
43
+ port: { type: String, required: true, default: 'main' },
44
+ sourceHandle: String,
45
+ targetHandle: String,
46
+ _id: false,
47
+ };
48
+ exports.MAIN_PORT = 'main';
49
+ /** Ports whose name is fixed, as opposed to the `<kind>:<id>` ones. */
50
+ exports.FIXED_PORTS = [
51
+ 'main',
52
+ 'else',
53
+ 'loop',
54
+ 'done',
55
+ 'rejected',
56
+ ];
57
+ /** Build the port name for a container-owned edge. */
58
+ const branchPort = (id) => `branch:${id}`;
59
+ exports.branchPort = branchPort;
60
+ const outputPort = (id) => `output:${id}`;
61
+ exports.outputPort = outputPort;
62
+ const categoryPort = (id) => `category:${id}`;
63
+ exports.categoryPort = categoryPort;
64
+ const rulePort = (id) => `rule:${id}`;
65
+ exports.rulePort = rulePort;
66
+ /** Read the container id back out of a port name, or null if the port is
67
+ * a fixed one. */
68
+ function portContainerId(port, kind) {
69
+ const prefix = `${kind}:`;
70
+ return port.startsWith(prefix) ? port.slice(prefix.length) : null;
71
+ }
72
+ /**
73
+ * The edges leaving `port`, in the shape the dispatcher wants.
74
+ *
75
+ * A ref written before Phase 1 has no `port` and means `main` — the same
76
+ * rule the schema default encodes, applied on read so a document that has
77
+ * not been touched since still routes correctly.
78
+ */
79
+ /* Deliberately loose in what it accepts: callers pass a hydrated Mongoose
80
+ * document, a raw driver `WithId<Document>`, or a plain object from a test,
81
+ * and all three carry the same field. */
82
+ function refsForPort(entity, port) {
83
+ const refs = (entity?.outputNodes ?? []);
84
+ return refs
85
+ .filter((n) => (n.port ?? exports.MAIN_PORT) === port)
86
+ .map((n) => ({
87
+ nodeType: n.nodeType,
88
+ nodeId: n.nodeId?.toString?.() ?? String(n.nodeId),
89
+ }));
90
+ }
@@ -0,0 +1,36 @@
1
+ /** The two fields ownership is read from, in that order (`_id` only labels the log line). */
2
+ interface OwnedDoc {
3
+ organizationId?: unknown;
4
+ userId?: unknown;
5
+ _id?: unknown;
6
+ }
7
+ /**
8
+ * Does this webhook belong to the organization whose pipeline is running?
9
+ *
10
+ * Every other node type reaches the pipeline through its service's
11
+ * `findOne(id, orgId)`, which filters by `organizationId` and so cannot return
12
+ * another tenant's node. Webhook-typed outputs were the one exception: they are
13
+ * resolved with a bare `webhookModel.findById(nodeId)` and were gated only on
14
+ * `isActive`. That `nodeId` comes from the tenant-written `outputNodes` array,
15
+ * whose only write-time constraint is `@IsMongoId()`, so naming a webhook in
16
+ * another organization made the pipeline create a child event on it — POSTed to
17
+ * the victim's `targetUrl`, signed with the victim's signing secret, written to
18
+ * their event history and pushed to their dashboard.
19
+ *
20
+ * Ownership is read as `(doc.organizationId ?? doc.userId)`, the same formula
21
+ * `WebhooksService.findOne` and `ingress.service.ts` use: `WebhooksService.create`
22
+ * stores the userId as organizationId when there is no org, so plain equality on
23
+ * that formula also matches rows written before that field existed.
24
+ *
25
+ * Callers skip rather than throw — a foreign target is dropped exactly like a
26
+ * deleted or paused one, so one bad edge cannot abort the sibling outputs of a
27
+ * pipeline that is otherwise the tenant's own.
28
+ *
29
+ * `orgId` is never attacker-supplied: every production caller derives it
30
+ * server-side from the source document. When it is missing we cannot establish
31
+ * ownership, so the answer is false — the only context that leaves it empty
32
+ * (`test-node.ts`) deliberately wires no `eventModel`/`webhookModel`/
33
+ * `onEventCreated` and so never reaches a dispatch site.
34
+ */
35
+ export declare function belongsToOrg(doc: OwnedDoc, orgId: unknown): boolean;
36
+ export {};
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.belongsToOrg = belongsToOrg;
4
+ const common_1 = require("@nestjs/common");
5
+ const logger = new common_1.Logger('OutputWebhookScope');
6
+ /**
7
+ * Does this webhook belong to the organization whose pipeline is running?
8
+ *
9
+ * Every other node type reaches the pipeline through its service's
10
+ * `findOne(id, orgId)`, which filters by `organizationId` and so cannot return
11
+ * another tenant's node. Webhook-typed outputs were the one exception: they are
12
+ * resolved with a bare `webhookModel.findById(nodeId)` and were gated only on
13
+ * `isActive`. That `nodeId` comes from the tenant-written `outputNodes` array,
14
+ * whose only write-time constraint is `@IsMongoId()`, so naming a webhook in
15
+ * another organization made the pipeline create a child event on it — POSTed to
16
+ * the victim's `targetUrl`, signed with the victim's signing secret, written to
17
+ * their event history and pushed to their dashboard.
18
+ *
19
+ * Ownership is read as `(doc.organizationId ?? doc.userId)`, the same formula
20
+ * `WebhooksService.findOne` and `ingress.service.ts` use: `WebhooksService.create`
21
+ * stores the userId as organizationId when there is no org, so plain equality on
22
+ * that formula also matches rows written before that field existed.
23
+ *
24
+ * Callers skip rather than throw — a foreign target is dropped exactly like a
25
+ * deleted or paused one, so one bad edge cannot abort the sibling outputs of a
26
+ * pipeline that is otherwise the tenant's own.
27
+ *
28
+ * `orgId` is never attacker-supplied: every production caller derives it
29
+ * server-side from the source document. When it is missing we cannot establish
30
+ * ownership, so the answer is false — the only context that leaves it empty
31
+ * (`test-node.ts`) deliberately wires no `eventModel`/`webhookModel`/
32
+ * `onEventCreated` and so never reaches a dispatch site.
33
+ */
34
+ function belongsToOrg(doc, orgId) {
35
+ const caller = orgId?.toString();
36
+ const owner = (doc.organizationId ?? doc.userId)?.toString();
37
+ if (caller && owner && owner === caller)
38
+ return true;
39
+ logger.warn(`[Tenancy] Output webhook ${doc._id?.toString() ?? '(unknown)'} belongs to ` +
40
+ `${owner ?? '(unknown)'}, not to ${caller ?? '(unknown)'} — skipped`);
41
+ return false;
42
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Truncate any value to fit within maxBytes when serialized.
3
+ * Used for telemetry metadata, notification payloadPreview, email preview, etc.
4
+ */
5
+ export declare function truncatePreview(value: unknown, maxBytes?: number): unknown;
6
+ /**
7
+ * Pretty-print payload as formatted JSON string, truncated to maxChars.
8
+ * Useful for {{payloadPreview}} in email/notification templates.
9
+ */
10
+ export declare function payloadPreview(payload: unknown, maxChars?: number): string;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.truncatePreview = truncatePreview;
4
+ exports.payloadPreview = payloadPreview;
5
+ /**
6
+ * Truncate any value to fit within maxBytes when serialized.
7
+ * Used for telemetry metadata, notification payloadPreview, email preview, etc.
8
+ */
9
+ function truncatePreview(value, maxBytes = 2048) {
10
+ if (value == null)
11
+ return undefined;
12
+ try {
13
+ const str = typeof value === 'string' ? value : JSON.stringify(value);
14
+ if (str.length <= maxBytes) {
15
+ return typeof value === 'string' ? value : JSON.parse(str);
16
+ }
17
+ return str.slice(0, maxBytes) + '...[truncated]';
18
+ }
19
+ catch {
20
+ return '[unserializable]';
21
+ }
22
+ }
23
+ /**
24
+ * Pretty-print payload as formatted JSON string, truncated to maxChars.
25
+ * Useful for {{payloadPreview}} in email/notification templates.
26
+ */
27
+ function payloadPreview(payload, maxChars = 2500) {
28
+ if (payload == null)
29
+ return '(no payload)';
30
+ try {
31
+ const str = JSON.stringify(payload, null, 2);
32
+ return str.length > maxChars
33
+ ? str.slice(0, maxChars) + '\n...[truncated]'
34
+ : str;
35
+ }
36
+ catch {
37
+ return '[unserializable]';
38
+ }
39
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Pipeline-level constants — magic numbers extracted for maintainability.
3
+ * Plan-level constants (SYNC_TIMEOUT_SECONDS, RESPONSE_MAX_BYTES) live in plans/plan.constants.ts.
4
+ */
5
+ /** Maximum recursion depth for dispatchOutputNodes — prevents infinite loops */
6
+ export declare const MAX_PROPAGATION_DEPTH = 200;
7
+ /** Default max node cycles per event when no org/workspace/plan override */
8
+ export declare const DEFAULT_MAX_NODE_CYCLES = 50;
9
+ /**
10
+ * Techo de elementos de un bucle cuando el plan no dice nada.
11
+ *
12
+ * Igual que `DEFAULT_MAX_NODE_CYCLES`: existe para que un límite ausente no
13
+ * signifique «sin límite». `maxLoopIterations` estuvo declarado en los planes
14
+ * y sin aplicar, y los tiers de pago valían `-1`; el resultado era que dentro
15
+ * de un bucle no había ningún techo y el tamaño de la lista lo elegía quien
16
+ * mandaba el evento.
17
+ *
18
+ * Se pone en el valor del plan más bajo a propósito: si un día se añade un
19
+ * tier y se olvida el campo, el respaldo aprieta en vez de abrir.
20
+ */
21
+ export declare const MAX_LOOP_ITERATIONS_POR_DEFECTO = 50;
22
+ /** Delay (ms) before re-dispatching recovered loops on startup — lets the app fully boot */
23
+ export declare const RECOVERY_BOOT_DELAY_MS = 5000;
24
+ /** Window (ms) for detecting stuck loops on startup — only loops older than this are recovered */
25
+ export declare const RECOVERY_WINDOW_MS: number;
26
+ /** Default per-node execution timeout (seconds) — used when NODE_TIMEOUT_DEFAULTS has no entry */
27
+ export declare const DEFAULT_NODE_TIMEOUT_SEC = 60;
28
+ /** Per-node-type execution timeouts (seconds). Nodes can override via entity.timeoutSeconds. */
29
+ export declare const NODE_TIMEOUT_DEFAULTS: Record<string, number>;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * Pipeline-level constants — magic numbers extracted for maintainability.
4
+ * Plan-level constants (SYNC_TIMEOUT_SECONDS, RESPONSE_MAX_BYTES) live in plans/plan.constants.ts.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.NODE_TIMEOUT_DEFAULTS = exports.DEFAULT_NODE_TIMEOUT_SEC = exports.RECOVERY_WINDOW_MS = exports.RECOVERY_BOOT_DELAY_MS = exports.MAX_LOOP_ITERATIONS_POR_DEFECTO = exports.DEFAULT_MAX_NODE_CYCLES = exports.MAX_PROPAGATION_DEPTH = void 0;
8
+ /** Maximum recursion depth for dispatchOutputNodes — prevents infinite loops */
9
+ exports.MAX_PROPAGATION_DEPTH = 200;
10
+ /** Default max node cycles per event when no org/workspace/plan override */
11
+ exports.DEFAULT_MAX_NODE_CYCLES = 50;
12
+ /**
13
+ * Techo de elementos de un bucle cuando el plan no dice nada.
14
+ *
15
+ * Igual que `DEFAULT_MAX_NODE_CYCLES`: existe para que un límite ausente no
16
+ * signifique «sin límite». `maxLoopIterations` estuvo declarado en los planes
17
+ * y sin aplicar, y los tiers de pago valían `-1`; el resultado era que dentro
18
+ * de un bucle no había ningún techo y el tamaño de la lista lo elegía quien
19
+ * mandaba el evento.
20
+ *
21
+ * Se pone en el valor del plan más bajo a propósito: si un día se añade un
22
+ * tier y se olvida el campo, el respaldo aprieta en vez de abrir.
23
+ */
24
+ exports.MAX_LOOP_ITERATIONS_POR_DEFECTO = 50;
25
+ /** Delay (ms) before re-dispatching recovered loops on startup — lets the app fully boot */
26
+ exports.RECOVERY_BOOT_DELAY_MS = 5_000;
27
+ /** Window (ms) for detecting stuck loops on startup — only loops older than this are recovered */
28
+ exports.RECOVERY_WINDOW_MS = 5 * 60 * 1000;
29
+ /** Default per-node execution timeout (seconds) — used when NODE_TIMEOUT_DEFAULTS has no entry */
30
+ exports.DEFAULT_NODE_TIMEOUT_SEC = 60;
31
+ /** Per-node-type execution timeouts (seconds). Nodes can override via entity.timeoutSeconds. */
32
+ exports.NODE_TIMEOUT_DEFAULTS = {
33
+ httpAction: 30,
34
+ firecrawlAction: 120,
35
+ sheetsAction: 60,
36
+ calendarAction: 60,
37
+ docsAction: 60,
38
+ mongoAction: 30,
39
+ emailAction: 15,
40
+ notificationAction: 15,
41
+ ai: 300,
42
+ transform: 30,
43
+ filter: 10,
44
+ code: 30,
45
+ markdown: 30,
46
+ cache: 10,
47
+ schemaValidator: 10,
48
+ split: 120,
49
+ conditional: 10,
50
+ aggregator: 30,
51
+ };
@@ -0,0 +1,58 @@
1
+ export interface RunnerContext {
2
+ url: string;
3
+ method: string;
4
+ headers: Record<string, string>;
5
+ body: string | undefined;
6
+ payload: unknown;
7
+ }
8
+ export interface RunnerResult {
9
+ url: unknown;
10
+ headers: unknown;
11
+ body: unknown;
12
+ }
13
+ /** Which isolation tier the probe settled on, for logging and tests. */
14
+ export declare function permissionTier(): string;
15
+ /** Top the spare pool back up, in the background. Failures are not fatal: the next run starts one itself. */
16
+ export declare function refillPool(): void;
17
+ /**
18
+ * Run one script in one throwaway child.
19
+ *
20
+ * `ctx.payload` must already be a plain, cloneable value — the caller does the
21
+ * same JSON round-trip the in-process sandbox always did, which both preserves
22
+ * that behaviour and guarantees the context can cross.
23
+ */
24
+ export declare function runInChild(code: string, ctx: RunnerContext, timeoutMs: number): Promise<RunnerResult>;
25
+ export interface CodeRunnerContext {
26
+ payload: unknown;
27
+ headers?: Record<string, unknown>;
28
+ meta?: Record<string, unknown>;
29
+ workspacePayloads?: Record<string, Record<string, unknown>>;
30
+ }
31
+ export interface CodeRunnerResult {
32
+ /** Lo que devolvió el código del inquilino. `null` cuando no devolvió nada. */
33
+ output: unknown;
34
+ logs: string[];
35
+ }
36
+ /**
37
+ * Ejecutar el código de un nodo de código en un hijo.
38
+ *
39
+ * Mismo pool y mismas garantías que el script pre-request: hijo desechable con
40
+ * entorno vacío, sin ficheros ni spawn donde el runtime lo soporte, y muerto
41
+ * después. Lo que cambia respecto a correrlo en proceso es dónde aterriza
42
+ * quien se salga del vm — y salirse del vm es fácil, nunca fue una frontera.
43
+ * Dentro del proceso de la API eso daba `process.env`, o sea `MONGO_URI` y el
44
+ * `API_SECRET` con el que se descifran las credenciales de todos los
45
+ * inquilinos. Aquí no da nada.
46
+ *
47
+ * Un fallo del propio código —una excepción, un timeout— vuelve como `Error`,
48
+ * con los logs que alcanzó a emitir enganchados en `logs`, porque el usuario
49
+ * los necesita justamente cuando algo revienta.
50
+ */
51
+ export declare function runCodeNodeInChild(code: string, ctx: CodeRunnerContext, timeoutMs: number): Promise<CodeRunnerResult>;
52
+ /**
53
+ * Stop every child, including the ones still starting, and refuse to start
54
+ * more. Registered on exit, and used by tests so jest sees no stray handles —
55
+ * a spare that finished starting after the last test would otherwise keep its
56
+ * IPC channel open.
57
+ */
58
+ export declare function shutdownPreRequestPool(): void;