UNPKG

n8n-nodes-mautic-advanced

Version:

Enhanced n8n node for Mautic with comprehensive API coverage including tags, campaigns, categories, and advanced contact management

227 lines (226 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runtimeZod = exports.RuntimeDynamicStructuredTool = void 0; // nodes/MauticAdvanced/ai-tools/runtime.ts const module_1 = require("module"); /** * Runtime resolution of `zod` and `@langchain/core`'s DynamicStructuredTool. * * IMPORTANT: resolution must NEVER run at module-import time. n8n's * node-directory-loader requires every node file in this package (including * this one) purely to register node metadata, long before any workflow * executes — and it aborts loading the ENTIRE package if any one file throws * while being required. Doing filesystem resolution here at import time is * exactly what took down the plain, non-AI `mauticAdvanced` node and its * trigger under pnpm-strict-isolated installs (issue #2). Resolution is * therefore deferred to first actual use inside the Proxy traps below, which * only fire when a connected AI tool is invoked at workflow-execution time. * * By execution time n8n's own Agent/MCP Trigger machinery has already loaded * `@n8n/n8n-nodes-langchain` (and therefore zod and @langchain/core) into the * process-global require.cache. We resolve the SAME module identity n8n uses — * the copy its `normalizeToolSchema` runs `instanceof ZodType` / `instanceof * ZodType`-style checks against — by two strategies, in order: * * 1. `require.main` — n8n's own entry point. For a correctly-installed * (non-isolated) host this lands on n8n's top-level copies directly. * Guarded for `require.main` being undefined (ESM launch, worker_threads / * queue-mode workers). We do NOT fall back to `__filename`: anchoring at * this file would resolve THIS package's own bundled copy, which is the * wrong module identity and fails n8n's `instanceof` checks silently. * * 2. An n8n-owned-tree anchor — a module already resident in require.cache * whose path belongs to a package only n8n owns and community nodes never * bundle. We `createRequire()` the dependency FROM that module's path, so * the resolved copy is tied to n8n's tree by identity — independent of * cache iteration order and of pnpm virtual-store path naming. * * If neither strategy resolves, we FAIL CLEAN (throw a diagnostic). We do NOT * scan require.cache for a bare `zod` / `@langchain/core` and guess which copy * is n8n's: under pnpm strict-isolated installs the cache key is the flat * virtual-store realpath (`.../.pnpm/zod@3.x.x/node_modules/zod/...`) which does * not encode the dependent package, so another installed community node's * bundled copy can win the scan and yield a wrong-instance `ZodType` that * n8n's `instanceof` check drops silently (issue #4). Returning nothing and * throwing is safer than returning a possibly-wrong copy. */ // n8n-owned anchor packages, in priority order. `@n8n/n8n-nodes-langchain` is // always loaded at execution time and is the code that runs the instanceof // checks, so resolving from it guarantees identical module identity. const N8N_ANCHOR_PACKAGES = [ { name: '@n8n/n8n-nodes-langchain', pattern: /[\\/]@n8n[\\/]n8n-nodes-langchain[\\/]/ }, { name: '@langchain/classic', pattern: /[\\/]@langchain[\\/]classic[\\/]/ }, { name: 'n8n-workflow', pattern: /[\\/]n8n-workflow[\\/]/ }, { name: 'n8n-core', pattern: /[\\/]n8n-core[\\/]/ }, ]; let _anchorDiagnostic = null; /** * Yields a createRequire() for EACH n8n-owned anchor package currently resident * in Node's process-global module cache, in the priority order of * N8N_ANCHOR_PACKAGES. Must be called lazily (not at module load) — n8n requires * node files for registration before any workflow runs, i.e. before the anchor * packages are necessarily resident in cache. * * IMPORTANT: this yields ALL available anchors rather than memoizing the first * one found. Different anchors resolve different dependencies: `n8n-workflow` / * `n8n-core` are imported by this package at registration time, so they are * resident FIRST — but they can resolve `zod` and NOT `@langchain/core/tools`. * Memoizing `n8n-workflow` as THE anchor would permanently starve * DynamicStructuredTool resolution even after `@langchain/*` later loads. So the * caller tries each anchor against the actual dependency and memoizes only at * the dependency level (on success). Re-scanning the cache each unresolved call * is cheap and only happens until the dependency resolves. */ function* iterN8nAnchorRequires() { let cache; try { // require.cache is the documented CommonJS alias for Module._cache, available // directly in CJS module scope (this file compiles to CJS via tsc). cache = require.cache; } catch (_e) { // best-effort — require.cache introspection is not guaranteed across Node versions cache = undefined; } if (!cache) { _anchorDiagnostic = 'require.cache unavailable'; return; } const keys = Object.keys(cache); const available = []; for (const anchor of N8N_ANCHOR_PACKAGES) { const key = keys.find((k) => anchor.pattern.test(k)); if (!key) continue; try { const req = (0, module_1.createRequire)(key); available.push(anchor.name); yield { req, source: `anchor ${anchor.name}` }; } catch (_e) { // createRequire failed for this anchor — try the next } } _anchorDiagnostic = available.length ? `anchors available: ${available.join(', ')}` : `no n8n-owned anchor resident in require.cache (tried ${N8N_ANCHOR_PACKAGES.map((a) => a.name).join(', ')})`; } /** * Yields candidate requires in resolution priority order: require.main first * (guarded for undefined), then EACH n8n-owned-tree anchor. Symmetric for every * dependency — no bare-cache scan, no self-exclusion. The caller must try the * requested dependency against each yielded require and stop at the first that * loads it. */ function* candidateRequires() { if (require.main?.filename) { try { yield { req: (0, module_1.createRequire)(require.main.filename), source: 'require.main' }; } catch (_e) { // require.main resolution unavailable — fall through to anchors } } yield* iterN8nAnchorRequires(); } let _RuntimeDynamicStructuredTool; let _runtimeZod; let _langchainLoadError = null; let _langchainDiagnostic = null; let _zodLoadError = null; let _zodDiagnostic = null; function resolveDynamicStructuredTool() { if (_RuntimeDynamicStructuredTool) return _RuntimeDynamicStructuredTool; const attempts = []; for (const { req, source } of candidateRequires()) { try { const coreTools = req('@langchain/core/tools'); if (typeof coreTools?.['DynamicStructuredTool'] === 'function') { _RuntimeDynamicStructuredTool = coreTools['DynamicStructuredTool']; _langchainDiagnostic = `resolved DynamicStructuredTool via ${source}`; _langchainLoadError = null; return _RuntimeDynamicStructuredTool; } attempts.push(`${source}: @langchain/core/tools lacked DynamicStructuredTool`); } catch (e) { attempts.push(`${source}: ${e.message}`); } } _langchainLoadError = attempts.length ? attempts.join('; ') : 'no n8n-owned resolution source available (require.main undefined, no anchor in cache)'; return undefined; } function resolveZod() { if (_runtimeZod) return _runtimeZod; const attempts = []; for (const { req, source } of candidateRequires()) { try { const z = req('zod'); // Validate the exports look like zod via `ZodType` (the class n8n's // normalizeToolSchema does `instanceof` against — the meaningful correctness // signal) plus the `object` factory our schema-generator calls. if (typeof z?.['ZodType'] === 'function' && typeof z?.['object'] === 'function') { _runtimeZod = z; _zodDiagnostic = `resolved zod via ${source}`; _zodLoadError = null; return _runtimeZod; } attempts.push(`${source}: module did not look like zod`); } catch (e) { attempts.push(`${source}: ${e.message}`); } } _zodLoadError = attempts.length ? attempts.join('; ') : 'no n8n-owned resolution source available (require.main undefined, no anchor in cache)'; return undefined; } // IMPORTANT: Proxy target MUST be `function () {}`, not `{}`. // ECMAScript spec §10.5.13: a Proxy only has [[Construct]] if its target does. // Plain objects lack [[Construct]], so `new Proxy({}, ...)` throws // "is not a constructor" before the construct trap ever fires. exports.RuntimeDynamicStructuredTool = new Proxy(function () { }, { construct(_target, args) { const ctor = resolveDynamicStructuredTool(); if (!ctor) { throw new Error(`[MauticAdvancedAiTools] Could not resolve LangChain's DynamicStructuredTool ` + `via require.main or an n8n-owned require.cache anchor. ` + `Ensure @n8n/n8n-nodes-langchain is installed in n8n's node_modules.` + (_anchorDiagnostic ? ` Anchor: ${_anchorDiagnostic}.` : '') + (_langchainDiagnostic ? ` ${_langchainDiagnostic}.` : '') + (_langchainLoadError ? ` Load error: ${_langchainLoadError}` : '')); } return new ctor(...args); }, get(_target, prop) { const ctor = resolveDynamicStructuredTool(); if (ctor) { return ctor[prop]; } return undefined; }, }); exports.runtimeZod = new Proxy({}, { get(_target, prop) { // Guard: frameworks probe Symbol.toPrimitive, Symbol.toStringTag, .then // (Promise thenable), and .constructor. Throwing on these causes // misleading errors during structural inspection. if (typeof prop === 'symbol' || prop === 'then' || prop === 'constructor') return undefined; const z = resolveZod(); if (!z) { throw new Error(`[MauticAdvancedAiTools] Could not resolve zod (accessing .${String(prop)}) ` + `via require.main or an n8n-owned require.cache anchor. ` + `Ensure @n8n/n8n-nodes-langchain is installed in n8n's node_modules.` + (_anchorDiagnostic ? ` Anchor: ${_anchorDiagnostic}.` : '') + (_zodDiagnostic ? ` ${_zodDiagnostic}.` : '') + (_zodLoadError ? ` Load error: ${_zodLoadError}` : '')); } return z[prop]; }, });