@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,496 @@
1
+ "use strict";
2
+ /**
3
+ * Load lastPayload for referenced nodes in a workspace.
4
+ *
5
+ * Used by $("node name") in templates — provides synchronous
6
+ * lookup of any node's output within the same workspace.
7
+ *
8
+ * Optimized with Redis caching:
9
+ * - Extract $() refs from entity → only load referenced nodes
10
+ * - Redis MGET first (~1ms) → MongoDB fallback only for cache misses
11
+ * - cacheNodePayload warms the cache → subsequent lookups are instant
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.getPayloadRedis = getPayloadRedis;
48
+ exports.initPayloadRedis = initPayloadRedis;
49
+ exports.extractCrossNodeRefs = extractCrossNodeRefs;
50
+ exports.cacheNodePayload = cacheNodePayload;
51
+ exports.deleteNodePayload = deleteNodePayload;
52
+ exports.invalidateCanvasCache = invalidateCanvasCache;
53
+ exports.propagateNodeRename = propagateNodeRename;
54
+ exports.loadWorkspacePayloads = loadWorkspacePayloads;
55
+ const mongodb_1 = require("mongodb");
56
+ const common_1 = require("@nestjs/common");
57
+ const swallow_1 = require("./swallow");
58
+ const try_parse_1 = require("./try-parse");
59
+ const workspacePayloadsLogger = new common_1.Logger('workspace-payloads');
60
+ // ── Singleton Redis client (lazy, shared across all calls) ──
61
+ let redisClient = null;
62
+ let redisInitAttempted = false;
63
+ /** Get the shared Redis client (or null if not initialized) */
64
+ function getPayloadRedis() {
65
+ return redisClient;
66
+ }
67
+ /** Get or create the shared Redis client for payload caching */
68
+ function initPayloadRedis(redisUrl) {
69
+ if (redisClient || redisInitAttempted)
70
+ return;
71
+ redisInitAttempted = true;
72
+ const url = redisUrl ?? process.env.REDIS_URL;
73
+ if (!url)
74
+ return;
75
+ try {
76
+ // Dynamic require — ioredis is a transitive dep of @nestjs/bullmq, not always directly installed
77
+ const IoRedis = require('ioredis');
78
+ redisClient = new IoRedis(url, {
79
+ maxRetriesPerRequest: 1,
80
+ lazyConnect: true,
81
+ });
82
+ redisClient.connect().catch(() => {
83
+ redisClient = null;
84
+ });
85
+ }
86
+ catch {
87
+ redisClient = null;
88
+ }
89
+ }
90
+ // ── Helpers ──
91
+ /** Extract node names referenced by $("name") or $('name') in the entity */
92
+ function extractCrossNodeRefs(entity) {
93
+ const refs = new Set();
94
+ // Match $("name"), $('name'), $(\"name\"), and $(\'name\') — handles JSON-escaped strings
95
+ const re = /\$\(\\?["']([^"'\\]+)\\?["']\)/g;
96
+ const visited = new WeakSet();
97
+ function scan(val) {
98
+ if (typeof val === 'string') {
99
+ let m;
100
+ while ((m = re.exec(val)) !== null)
101
+ refs.add(m[1]);
102
+ re.lastIndex = 0;
103
+ }
104
+ else if (val && typeof val === 'object') {
105
+ if (visited.has(val))
106
+ return;
107
+ visited.add(val);
108
+ // Mongoose document → use toObject() to get plain data without circular refs
109
+ const plain = typeof val.toObject === 'function'
110
+ ? val.toObject()
111
+ : val;
112
+ if (Array.isArray(plain)) {
113
+ for (const item of plain)
114
+ scan(item);
115
+ }
116
+ else {
117
+ for (const v of Object.values(plain))
118
+ scan(v);
119
+ }
120
+ }
121
+ }
122
+ scan(entity);
123
+ return refs;
124
+ }
125
+ function payloadKey(wsId, name) {
126
+ return `ws:${wsId}:payload:${name}`;
127
+ }
128
+ /** Write a node's payload to Redis cache (fire-and-forget, 5 min TTL) */
129
+ function cacheNodePayload(wsId, name, payload) {
130
+ if (!redisClient)
131
+ return;
132
+ redisClient
133
+ .set(payloadKey(wsId, name), JSON.stringify(payload), 'EX', 300)
134
+ .catch((0, swallow_1.onFailure)(workspacePayloadsLogger, `workspace payload cache write ${wsId}/${name}`));
135
+ }
136
+ /**
137
+ * Drop a deleted node's payload row and its Redis mirror.
138
+ *
139
+ * `saveLastPayload` creates the row and nothing ever removed it. Since the
140
+ * payload moved out of the entity and into `nodepayloads`, deleting a node
141
+ * left its output behind permanently: measured on production 2026-08-05,
142
+ * 25 of 192 rows (13%) belonged to nodes that no longer existed, spread
143
+ * across 13 node types, the oldest from 2026-07-31.
144
+ *
145
+ * The stale Redis key is the sharper edge. `$("node name")` resolves by
146
+ * NAME, not by id, so a new node reusing a deleted one's name would read the
147
+ * dead payload until the 5-minute TTL expired.
148
+ *
149
+ * Best-effort: the entity delete has already happened and a leftover row is
150
+ * inert, so failures are logged rather than thrown back at the caller.
151
+ */
152
+ async function deleteNodePayload(connection, nodeId) {
153
+ const db = connection.db;
154
+ if (!db)
155
+ return;
156
+ const oid = new mongodb_1.ObjectId(nodeId);
157
+ const rows = db.collection('nodepayloads');
158
+ // Read before deleting. The row is the only place carrying the workspace
159
+ // and name needed to evict the Redis key — once it is gone there is
160
+ // nothing left to derive them from, which is how the row and its cache
161
+ // entry would drift apart.
162
+ const row = await rows
163
+ .findOne({ nodeId: oid }, { projection: { name: 1, workspaceId: 1 } })
164
+ .catch((0, swallow_1.onFailureReturn)(workspacePayloadsLogger, `nodepayloads lookup before delete for node ${nodeId}`, null));
165
+ await rows
166
+ .deleteOne({ nodeId: oid })
167
+ .catch((0, swallow_1.onFailure)(workspacePayloadsLogger, `nodepayloads delete for node ${nodeId}`));
168
+ if (redisClient && row?.workspaceId && row?.name) {
169
+ redisClient
170
+ .del(payloadKey(String(row.workspaceId), String(row.name)))
171
+ .catch((0, swallow_1.onFailure)(workspacePayloadsLogger, `payload cache purge for deleted node ${nodeId} (${String(row.name)})`));
172
+ }
173
+ }
174
+ /** Invalidate canvas-data Redis cache for an org+workspace (fire-and-forget) */
175
+ function invalidateCanvasCache(orgId, workspaceId) {
176
+ /* El cliente se creaba sólo al ejecutar un nodo, correr un pipeline o probar
177
+ uno — nunca al arrancar. Así que en un proceso recién desplegado, o que
178
+ todavía no haya despachado nada, `redisClient` era null y esta función se
179
+ iba por el `return` de abajo sin borrar nada... mientras CanvasService, que
180
+ sí crea el suyo en el constructor, seguía sirviendo el lienzo cacheado 30 s.
181
+ El efecto para quien usa: creas un nodo, recargas, y el lienzo aparece sin
182
+ él; borras uno, recargas, y vuelve. Inicializar aquí ata la invalidación al
183
+ mismo arranque perezoso que el caché en vez de a que alguien haya disparado
184
+ un nodo antes. */
185
+ initPayloadRedis();
186
+ if (!redisClient)
187
+ return;
188
+ const keys = [`canvas:${orgId}:all`];
189
+ if (workspaceId)
190
+ keys.push(`canvas:${orgId}:${workspaceId}`);
191
+ redisClient
192
+ .del(...keys)
193
+ .catch((0, swallow_1.onFailure)(workspacePayloadsLogger, `workspace payload cache purge (${keys.length} key(s))`));
194
+ }
195
+ /**
196
+ * Propagate a node rename to the `nodepayloads` collection and the
197
+ * Redis hot cache. Without this, cross-node `$("name")` lookups break
198
+ * silently after a rename: the template engine resolves $() by NAME
199
+ * (the data-picker emits names, not ObjectIds), but the persisted
200
+ * nodepayloads row keeps the old auto-generated name until the next
201
+ * dispatch overwrites it. The user-visible bug is "node fired, payload
202
+ * shows in the detail page, but downstream `$()` references render
203
+ * empty" — which produces hours of debugging chasing ghost workspace
204
+ * mismatches.
205
+ *
206
+ * Also wipes the old Redis cache key so a stale read doesn't shadow
207
+ * the new name, and re-warms under the new name when the row has a
208
+ * payload (so the very next $() lookup hits Redis, not Mongo).
209
+ *
210
+ * Fire-and-forget on individual failures — caller already returned
211
+ * the updated entity. Best-effort: the nodepayloads row is overwritten
212
+ * on the next dispatch anyway, so a one-off failure is recoverable.
213
+ */
214
+ async function propagateNodeRename(connection, nodeId, previousName, newName, workspaceId) {
215
+ if (!previousName || previousName === newName)
216
+ return;
217
+ const ObjectIdCtor = (await Promise.resolve().then(() => __importStar(require('mongoose')))).Types.ObjectId;
218
+ try {
219
+ await connection.db
220
+ ?.collection('nodepayloads')
221
+ .updateOne({ nodeId: new ObjectIdCtor(nodeId) }, { $set: { name: newName } });
222
+ }
223
+ catch (err) {
224
+ workspacePayloadsLogger.warn(`propagateNodeRename — nodepayloads update failed for ${nodeId}: ${err.message}`);
225
+ return;
226
+ }
227
+ if (!workspaceId || !redisClient)
228
+ return;
229
+ try {
230
+ redisClient
231
+ .del(payloadKey(workspaceId, previousName))
232
+ .catch((0, swallow_1.onFailure)(workspacePayloadsLogger, `workspace payload cache rename purge ${workspaceId}/${previousName}`));
233
+ const fresh = await connection.db
234
+ ?.collection('nodepayloads')
235
+ .findOne({ nodeId: new ObjectIdCtor(nodeId) }, { projection: { payload: 1 } });
236
+ if (fresh?.payload) {
237
+ cacheNodePayload(workspaceId, newName, fresh.payload);
238
+ }
239
+ }
240
+ catch (err) {
241
+ // Redis is best-effort here, but this is the rename path: failing leaves
242
+ // the payload cached under the OLD node name, so `$("New Name")` resolves
243
+ // to nothing while `$("Old Name")` still works. Confusing enough to be
244
+ // worth a line.
245
+ workspacePayloadsLogger.warn(`workspace payload cache re-key ${workspaceId}: ${previousName} → ${newName} failed: ${err instanceof Error ? err.message : String(err)}`);
246
+ }
247
+ }
248
+ // ── Main loader ──
249
+ /**
250
+ * Load workspace payloads for the given node names.
251
+ * Redis first (MGET, ~1ms), MongoDB fallback only for cache misses.
252
+ * If nodeNames is empty or undefined, returns {} instantly.
253
+ */
254
+ async function loadWorkspacePayloads(db, workspaceId, orgId, nodeNames) {
255
+ if (!nodeNames || nodeNames.size === 0)
256
+ return {};
257
+ const names = Array.from(nodeNames);
258
+ const result = {};
259
+ // 1. Try Redis MGET (single round-trip, ~1ms)
260
+ if (redisClient) {
261
+ const keys = names.map((n) => payloadKey(workspaceId, n));
262
+ // A per-entry parse failure is an ordinary miss — the entry is stale or
263
+ // truncated and Mongo answers instead. The MGET itself failing is not: it
264
+ // means every lookup in this workspace is going to Mongo from now on, which
265
+ // is a performance cliff nobody would connect to Redis without a line here.
266
+ const values = await redisClient
267
+ .mget(...keys)
268
+ .catch((0, swallow_1.onFailureReturn)(workspacePayloadsLogger, `workspace payload cache read ${workspaceId} (${names.length} nodes)`, null));
269
+ if (values) {
270
+ const misses = [];
271
+ for (let i = 0; i < names.length; i++) {
272
+ const parsed = (0, try_parse_1.tryParseJsonObject)(values[i]);
273
+ if (parsed)
274
+ result[names[i]] = parsed;
275
+ else
276
+ misses.push(names[i]);
277
+ }
278
+ if (misses.length === 0)
279
+ return result;
280
+ names.length = 0;
281
+ names.push(...misses);
282
+ }
283
+ }
284
+ // 2. MongoDB fallback — single query to node_payloads collection (not 30 collection scan)
285
+ const docs = await db
286
+ .collection('nodepayloads')
287
+ .find({
288
+ workspaceId: new mongodb_1.ObjectId(workspaceId),
289
+ organizationId: new mongodb_1.ObjectId(orgId),
290
+ name: { $in: names },
291
+ }, { projection: { name: 1, payload: 1 } })
292
+ .toArray();
293
+ for (const doc of docs) {
294
+ if (doc.name && doc.payload) {
295
+ result[doc.name] = doc.payload;
296
+ cacheNodePayload(workspaceId, doc.name, doc.payload);
297
+ }
298
+ }
299
+ // Rename-recovery fallback — when nodepayloads has the OLD name but
300
+ // the entity has been renamed in the dashboard. The on-disk row
301
+ // gets its `name` field updated only at next dispatch (or now
302
+ // synchronously via propagateNodeRename on entity update), but
303
+ // existing rows from before that fix are still stuck on the old
304
+ // name. We scan the entity collections by the CURRENT (requested)
305
+ // name, get the nodeId, then re-resolve nodepayloads by nodeId.
306
+ // That bridges the gap without requiring users to re-fire every
307
+ // upstream trigger or run a manual migration.
308
+ //
309
+ // Also serves as the legacy fallback for triggers whose
310
+ // dashboard Output panel reads entity.lastPayload directly — if
311
+ // an entity carries lastPayload but its nodepayloads row is
312
+ // missing entirely, we surface entity.lastPayload too.
313
+ const initiallyRequested = Array.from(nodeNames);
314
+ let resolved = new Set(Object.keys(result));
315
+ let stillMissing = initiallyRequested.filter((n) => !resolved.has(n));
316
+ const initialMissingCount = stillMissing.length;
317
+ if (stillMissing.length > 0) {
318
+ const FALLBACK_COLLECTIONS = [
319
+ 'triggers',
320
+ 'chattriggers',
321
+ 'scheduledworkflows',
322
+ 'webhooks',
323
+ ];
324
+ const collectedNodeIds = [];
325
+ const nodeIdToName = new Map();
326
+ for (const coll of FALLBACK_COLLECTIONS) {
327
+ if (stillMissing.length === 0)
328
+ break;
329
+ try {
330
+ const entities = await db
331
+ .collection(coll)
332
+ .find({
333
+ organizationId: new mongodb_1.ObjectId(orgId),
334
+ workspaceId: new mongodb_1.ObjectId(workspaceId),
335
+ name: { $in: stillMissing },
336
+ }, { projection: { name: 1, _id: 1, lastPayload: 1 } })
337
+ .toArray();
338
+ for (const ent of entities) {
339
+ const name = ent.name;
340
+ if (!name)
341
+ continue;
342
+ collectedNodeIds.push(ent._id);
343
+ nodeIdToName.set(ent._id.toString(), name);
344
+ // Direct hit via legacy entity.lastPayload (rare but still valid)
345
+ const payload = ent.lastPayload;
346
+ if (payload && !(name in result)) {
347
+ result[name] = payload;
348
+ cacheNodePayload(workspaceId, name, payload);
349
+ }
350
+ }
351
+ stillMissing = stillMissing.filter((n) => !(n in result));
352
+ }
353
+ catch {
354
+ /* collection doesn't exist or query failed — try next */
355
+ }
356
+ }
357
+ // Re-resolve via nodepayloads by nodeId (catches renamed rows
358
+ // whose `name` field is still stuck on the previous value).
359
+ if (stillMissing.length > 0 && collectedNodeIds.length > 0) {
360
+ try {
361
+ const docs = await db
362
+ .collection('nodepayloads')
363
+ .find({ nodeId: { $in: collectedNodeIds } }, { projection: { nodeId: 1, payload: 1 } })
364
+ .toArray();
365
+ for (const doc of docs) {
366
+ const nid = doc.nodeId.toString();
367
+ const wantedName = nodeIdToName.get(nid);
368
+ const payload = doc.payload;
369
+ if (wantedName && payload && !(wantedName in result)) {
370
+ result[wantedName] = payload;
371
+ // Repair the stale name on the row so the next lookup
372
+ // hits the fast path. Best-effort — log on failure
373
+ // but don't fail the read.
374
+ db.collection('nodepayloads')
375
+ .updateOne({ _id: doc._id }, { $set: { name: wantedName } })
376
+ .catch((err) => workspacePayloadsLogger.warn(`lazy rename repair failed for nodeId=${nid}: ${err.message}`));
377
+ cacheNodePayload(workspaceId, wantedName, payload);
378
+ }
379
+ }
380
+ stillMissing = stillMissing.filter((n) => !(n in result));
381
+ }
382
+ catch (err) {
383
+ workspacePayloadsLogger.warn(`loadWorkspacePayloads — nodeId re-resolve failed: ${err.message}`);
384
+ }
385
+ }
386
+ if (initialMissingCount > stillMissing.length) {
387
+ workspacePayloadsLogger.log(`loadWorkspacePayloads — fallback resolved ${initialMissingCount - stillMissing.length} name(s) (rename-recovery path) in ws=${workspaceId}`);
388
+ }
389
+ resolved = new Set(Object.keys(result));
390
+ }
391
+ // Diagnostic: every requested name that didn't land in the result
392
+ // got resolved as `undefined` by the template engine. Surface the
393
+ // mismatch so users can debug name-spelling / case / whitespace
394
+ // without having to inspect the database. Also dump the names that
395
+ // DO exist in the workspace so the diff is obvious — most "no
396
+ // resolve" cases turn out to be silent casing / whitespace drift.
397
+ const missing = initiallyRequested.filter((n) => !resolved.has(n));
398
+ if (missing.length > 0) {
399
+ try {
400
+ // Cross-workspace probe — find the missing names ANYWHERE in
401
+ // the org's nodepayloads. If they live under a different
402
+ // workspaceId (or null), that's the diagnosis.
403
+ const crossWs = await db
404
+ .collection('nodepayloads')
405
+ .find({
406
+ organizationId: new mongodb_1.ObjectId(orgId),
407
+ name: { $in: missing },
408
+ }, { projection: { name: 1, workspaceId: 1, nodeType: 1, nodeId: 1 } })
409
+ .limit(20)
410
+ .toArray();
411
+ const crossInfo = crossWs.length > 0
412
+ ? crossWs
413
+ .map((d) => {
414
+ const wsRaw = d.workspaceId;
415
+ const wsId = wsRaw ? wsRaw.toString() : 'null';
416
+ const wsLabel = wsId === workspaceId
417
+ ? `${wsId} (SAME — name match failed for unknown reason)`
418
+ : wsId === 'null'
419
+ ? 'NULL (legacy node — never assigned a workspace)'
420
+ : `${wsId} (DIFFERENT — workspaceId MISMATCH)`;
421
+ return `"${d.name}" (${d.nodeType ?? '?'}) → ws=${wsLabel} node=${d.nodeId?.toString() ?? '?'}`;
422
+ })
423
+ .join('; ')
424
+ : '(not found in any workspace of this org — node never fired)';
425
+ // Deeper probe — also scan ENTITY collections cross-workspace.
426
+ // The dashboard's OutputSection reads `entity.lastPayload`
427
+ // directly, so a user can see data in the UI even when both
428
+ // nodepayloads AND the same-workspace entity scan come up
429
+ // empty (means the entity lives in a different workspace
430
+ // entirely). Surface ALL matches across orgs so the workspace
431
+ // mismatch is unambiguous.
432
+ const entityFindings = [];
433
+ for (const coll of [
434
+ 'triggers',
435
+ 'chattriggers',
436
+ 'scheduledworkflows',
437
+ 'webhooks',
438
+ ]) {
439
+ try {
440
+ const hits = await db
441
+ .collection(coll)
442
+ .find({
443
+ organizationId: new mongodb_1.ObjectId(orgId),
444
+ name: { $in: missing },
445
+ }, {
446
+ projection: {
447
+ name: 1,
448
+ workspaceId: 1,
449
+ lastPayload: 1,
450
+ isActive: 1,
451
+ },
452
+ })
453
+ .limit(10)
454
+ .toArray();
455
+ for (const ent of hits) {
456
+ const wsRaw = ent.workspaceId;
457
+ const wsId = wsRaw ? wsRaw.toString() : 'null';
458
+ const wsLabel = wsId === workspaceId ? 'SAME ws' : `DIFFERENT ws (${wsId})`;
459
+ const hasLp = ent.lastPayload != null &&
460
+ Object.keys(ent.lastPayload).length > 0;
461
+ entityFindings.push(`"${ent.name}" in ${coll} → ${wsLabel}, lastPayload=${hasLp ? 'YES' : 'EMPTY'}, isActive=${ent.isActive ?? '?'}`);
462
+ }
463
+ }
464
+ catch {
465
+ /* collection doesn't exist — skip */
466
+ }
467
+ }
468
+ const entityInfo = entityFindings.length > 0
469
+ ? entityFindings.join('; ')
470
+ : '(no matching entity anywhere in the org)';
471
+ const all = await db
472
+ .collection('nodepayloads')
473
+ .find({
474
+ workspaceId: new mongodb_1.ObjectId(workspaceId),
475
+ organizationId: new mongodb_1.ObjectId(orgId),
476
+ }, { projection: { name: 1, nodeType: 1 } })
477
+ .limit(50)
478
+ .toArray();
479
+ const availableNames = all
480
+ .map((d) => {
481
+ const name = d.name ?? '(no name)';
482
+ const nodeType = d.nodeType ?? '?';
483
+ return `"${name}" (${nodeType})`;
484
+ })
485
+ .join(', ');
486
+ workspacePayloadsLogger.warn(`loadWorkspacePayloads — ${missing.length}/${initiallyRequested.length} unresolved in ws=${workspaceId}: missing=[${missing.map((m) => `"${m}"`).join(', ')}]. ` +
487
+ `\n → nodepayloads cross-org: ${crossInfo}.` +
488
+ `\n → entity collections cross-org: ${entityInfo}.` +
489
+ `\n → Available payloads in THIS workspace (${all.length}): [${availableNames || 'none'}].`);
490
+ }
491
+ catch (err) {
492
+ workspacePayloadsLogger.warn(`loadWorkspacePayloads — ${missing.length} unresolved in ws=${workspaceId}: [${missing.join(', ')}] (diagnostic query failed: ${err.message})`);
493
+ }
494
+ }
495
+ return result;
496
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@hostwebhook/node-sdk",
3
+ "version": "0.1.0",
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
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "npm --prefix ../node-types run build && npm --prefix ../template-engine run build && tsc",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "keywords": [
15
+ "hostwebhook",
16
+ "nodes",
17
+ "sdk"
18
+ ],
19
+ "license": "MIT",
20
+ "peerDependencies": {
21
+ "@hostwebhook/node-types": ">=1.67.0",
22
+ "@hostwebhook/template-engine": ">=2.0.0",
23
+ "@nestjs/common": ">=11.0.0",
24
+ "class-transformer": ">=0.5.0",
25
+ "express": ">=5.0.0",
26
+ "mongodb": ">=6.0.0",
27
+ "mongoose": ">=8.0.0",
28
+ "re2": ">=1.20.0",
29
+ "undici": ">=7.0.0",
30
+ "class-validator": ">=0.14.0"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5.0.0"
34
+ }
35
+ }