@hostwebhook/node-sdk 0.3.0 → 0.4.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/acceso-al-workspace.d.ts +51 -0
- package/dist/acceso-al-workspace.js +99 -0
- package/dist/base-node.service.d.ts +120 -0
- package/dist/base-node.service.js +409 -0
- package/dist/cascade-cleanup.d.ts +13 -0
- package/dist/cascade-cleanup.js +93 -0
- package/dist/credential-references.d.ts +131 -0
- package/dist/credential-references.js +210 -0
- package/dist/dto-utils.d.ts +11 -0
- package/dist/dto-utils.js +22 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/limpiar-marcador-detach.d.ts +63 -0
- package/dist/limpiar-marcador-detach.js +92 -0
- package/dist/olvidar-la-colocacion.d.ts +23 -0
- package/dist/olvidar-la-colocacion.js +60 -0
- package/dist/sync-connections.d.ts +73 -0
- package/dist/sync-connections.js +120 -0
- package/dist/unique-name.d.ts +6 -0
- package/dist/unique-name.js +32 -0
- package/package.json +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertNoSelfConnection = assertNoSelfConnection;
|
|
4
|
+
exports.assertConnectableTypes = assertConnectableTypes;
|
|
5
|
+
exports.validateConnectionWorkspaces = validateConnectionWorkspaces;
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
const mongoose_1 = require("mongoose");
|
|
8
|
+
const node_types_1 = require("@hostwebhook/node-types");
|
|
9
|
+
const node_dispatch_registry_1 = require("./node-dispatch.registry");
|
|
10
|
+
const output_node_dto_1 = require("./dto/output-node.dto");
|
|
11
|
+
/** Map nodeType → MongoDB collection name */
|
|
12
|
+
const TYPE_TO_COLLECTION = {};
|
|
13
|
+
for (const entry of node_dispatch_registry_1.NODE_DISPATCH_REGISTRY) {
|
|
14
|
+
TYPE_TO_COLLECTION[entry.type] = entry.collection;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Reject a node wired to itself.
|
|
18
|
+
*
|
|
19
|
+
* A canvas drag from a node's own output dot to its own input dot produced a
|
|
20
|
+
* real, persisted edge: source and target were the same id. Nothing stopped it
|
|
21
|
+
* — the dashboard assumed the registry prevented invalid connections, and the
|
|
22
|
+
* registry answers a different question. `canReceiveFrom('transform',
|
|
23
|
+
* 'transform')` is TRUE, and correctly so: chaining two different Transform
|
|
24
|
+
* nodes is ordinary. What the type pair cannot express is "not this same
|
|
25
|
+
* instance", because ids are not types.
|
|
26
|
+
*
|
|
27
|
+
* The one legitimate self-edge is the Loop node's loop-back, and the registry
|
|
28
|
+
* already declares it as `special.hasLoopBack`. So the exception is read from
|
|
29
|
+
* there rather than hardcoded here — a future node that needs to re-enter
|
|
30
|
+
* itself declares the flag and both the canvas and this check follow.
|
|
31
|
+
*/
|
|
32
|
+
function assertNoSelfConnection(nodeType, nodeId, nodes) {
|
|
33
|
+
if (node_types_1.NODE_CONNECTIONS[nodeType]?.special?.hasLoopBack)
|
|
34
|
+
return;
|
|
35
|
+
const selfId = nodeId.toString();
|
|
36
|
+
for (const node of nodes) {
|
|
37
|
+
if (node.nodeType === nodeType && node.nodeId?.toString() === selfId) {
|
|
38
|
+
throw new common_1.BadRequestException(`A ${nodeType} node cannot connect to itself`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Rechaza una conexión entre tipos que no pueden conectarse.
|
|
44
|
+
*
|
|
45
|
+
* Hasta ahora la compatibilidad de tipos era cosa del lienzo, y estaba escrito
|
|
46
|
+
* aquí al lado: *"Type compatibility is the registry's job and is checked on
|
|
47
|
+
* the canvas"*. Eso valía mientras el lienzo fuera el único que escribe, y no
|
|
48
|
+
* lo es: `/agent-context` le explica a un agente, con ejemplos, que meta
|
|
49
|
+
* `{ nodeType, nodeId }` a mano en `inputNodes`/`outputNodes`, y cualquier
|
|
50
|
+
* cliente con la clave y el scope puede hacer lo mismo.
|
|
51
|
+
*
|
|
52
|
+
* Lo que se colaba por ahí no revienta: **desaparece**. `dispatchOutputNodes`
|
|
53
|
+
* cae en su rama por defecto, `getNodeHandler('trigger')` no devuelve nada y
|
|
54
|
+
* suelta un `logger.warn` —sin evento de telemetría, porque el `telemetry.emit`
|
|
55
|
+
* está en el `catch` y esto no lanza—. O sea una arista dibujada en el lienzo
|
|
56
|
+
* que no dispara nunca y que no aparece en ningún sitio donde mirarla.
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ **La regla es `fromNodes`/`toNodes` de `NODE_UI`, y no las listas de
|
|
59
|
+
* `NODE_CONNECTIONS`.** La tentación es usar `acceptsInputFrom`/`canOutputTo`,
|
|
60
|
+
* que están ahí mismo, las declaran los 43 tipos y parecen más precisas. Están
|
|
61
|
+
* podridas: medidas contra producción el 2026-08-19 rechazaban **111 de 364**
|
|
62
|
+
* conexiones que funcionan hoy — `trigger → ai`, `ai → telegramAction`,
|
|
63
|
+
* `sheetsAction → filter`. `fromNodes`/`toNodes` rechazaba **0**, porque es el
|
|
64
|
+
* mismo dato que decide qué puntos pinta la tarjeta. Endurecer con eso no
|
|
65
|
+
* puede romper nada que el usuario haya podido crear desde el lienzo, que es
|
|
66
|
+
* justo la propiedad que se busca: la api no debe ser más estricta que la
|
|
67
|
+
* pantalla, sólo igual de estricta en los caminos que la pantalla no cubre.
|
|
68
|
+
*
|
|
69
|
+
* De un tipo que el registro no conoce no se opina. Un alias viejo se
|
|
70
|
+
* normaliza primero (`voiceCall` → `voiceAgent`); lo que ni así aparezca se
|
|
71
|
+
* deja pasar, porque un 400 sobre datos heredados con autoguardado deja la
|
|
72
|
+
* página sin poder guardar nunca más.
|
|
73
|
+
*/
|
|
74
|
+
function assertConnectableTypes(ownType, refs) {
|
|
75
|
+
// En `outputNodes` yo soy el origen y la referencia es el destino.
|
|
76
|
+
for (const ref of refs.outputNodes ?? [])
|
|
77
|
+
assertPar(ownType, (0, output_node_dto_1.normalizeNodeType)(ref.nodeType));
|
|
78
|
+
// En `inputNodes` es al revés.
|
|
79
|
+
for (const ref of refs.inputNodes ?? [])
|
|
80
|
+
assertPar((0, output_node_dto_1.normalizeNodeType)(ref.nodeType), ownType);
|
|
81
|
+
}
|
|
82
|
+
function assertPar(origen, destino) {
|
|
83
|
+
const uiOrigen = node_types_1.NODE_UI[origen];
|
|
84
|
+
const uiDestino = node_types_1.NODE_UI[destino];
|
|
85
|
+
if (!uiOrigen || !uiDestino)
|
|
86
|
+
return;
|
|
87
|
+
if (uiOrigen.toNodes !== true) {
|
|
88
|
+
throw new common_1.BadRequestException(`A ${origen} node has no output — nothing can start from it`);
|
|
89
|
+
}
|
|
90
|
+
if (uiDestino.fromNodes !== true) {
|
|
91
|
+
throw new common_1.BadRequestException(`A ${destino} node has no input — it can only start a flow`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Validate that all target nodes belong to the same workspace as the source node.
|
|
96
|
+
* Throws BadRequestException if any cross-workspace connection is detected.
|
|
97
|
+
*
|
|
98
|
+
* This is the ONLY backend connection logic. The frontend owns connection
|
|
99
|
+
* creation/deletion — it updates both sides (source.outputNodes + target.inputNodes).
|
|
100
|
+
* The backend just saves what it receives and validates workspace boundaries.
|
|
101
|
+
*/
|
|
102
|
+
async function validateConnectionWorkspaces(connection, sourceWorkspaceId, nodes) {
|
|
103
|
+
if (!sourceWorkspaceId)
|
|
104
|
+
return;
|
|
105
|
+
const srcWsStr = sourceWorkspaceId.toString();
|
|
106
|
+
for (const node of nodes) {
|
|
107
|
+
const col = TYPE_TO_COLLECTION[node.nodeType];
|
|
108
|
+
if (!col)
|
|
109
|
+
continue;
|
|
110
|
+
const doc = await connection
|
|
111
|
+
.collection(col)
|
|
112
|
+
.findOne({ _id: new mongoose_1.Types.ObjectId(node.nodeId) }, { projection: { workspaceId: 1 } });
|
|
113
|
+
if (!doc)
|
|
114
|
+
continue;
|
|
115
|
+
const targetWs = doc.workspaceId?.toString() ?? null;
|
|
116
|
+
if (targetWs && targetWs !== srcWsStr) {
|
|
117
|
+
throw new common_1.BadRequestException(`Cannot connect nodes across workspaces (source workspace: ${srcWsStr}, target workspace: ${targetWs})`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Connection } from 'mongoose';
|
|
2
|
+
/**
|
|
3
|
+
* Ensures no other node in the workspace already uses this name.
|
|
4
|
+
* Searches across ALL node collections (cross-type uniqueness).
|
|
5
|
+
*/
|
|
6
|
+
export declare function assertUniqueNodeName(connection: Connection, workspaceId: any, name: string, excludeId?: string): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertUniqueNodeName = assertUniqueNodeName;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const mongoose_1 = require("mongoose");
|
|
6
|
+
const node_types_1 = require("@hostwebhook/node-types");
|
|
7
|
+
const SKIP_TYPES = new Set(['stickyNote']);
|
|
8
|
+
const MAX_NODE_NAME_LENGTH = 50;
|
|
9
|
+
/**
|
|
10
|
+
* Ensures no other node in the workspace already uses this name.
|
|
11
|
+
* Searches across ALL node collections (cross-type uniqueness).
|
|
12
|
+
*/
|
|
13
|
+
async function assertUniqueNodeName(connection, workspaceId, name, excludeId) {
|
|
14
|
+
if (!name)
|
|
15
|
+
return;
|
|
16
|
+
if (name.length > MAX_NODE_NAME_LENGTH) {
|
|
17
|
+
throw new common_1.BadRequestException(`Node name must be ${MAX_NODE_NAME_LENGTH} characters or less (got ${name.length}).`);
|
|
18
|
+
}
|
|
19
|
+
const wsId = workspaceId ? new mongoose_1.Types.ObjectId(workspaceId) : null;
|
|
20
|
+
for (const [type, config] of Object.entries(node_types_1.NODE_DISPATCH)) {
|
|
21
|
+
if (SKIP_TYPES.has(type))
|
|
22
|
+
continue;
|
|
23
|
+
const col = connection.collection(config.collection);
|
|
24
|
+
const filter = { name, workspaceId: wsId };
|
|
25
|
+
if (excludeId)
|
|
26
|
+
filter._id = { $ne: new mongoose_1.Types.ObjectId(excludeId) };
|
|
27
|
+
const existing = await col.findOne(filter, { projection: { _id: 1 } });
|
|
28
|
+
if (existing) {
|
|
29
|
+
throw new common_1.ConflictException(`A node named "${name}" already exists in this workspace. Please choose a different name.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hostwebhook/node-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "El SDK de un nodo de HostWebhook: ciclo de vida, ejecución con iteración, reintentos, filtros y validación de esquema — lo que comparten la api y el futuro servicio de nodos",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|